Significant refactorings throughout the data protection stack

- Move IDataProtectionProvider, IDataProtector, and extension methods to their own package
- Simplify the APIs for registering and configuring the system
- Default implementation now auto-detects capabilities of OS
- Use EncryptedXml for X.509 certificate-based encryption
- Add ability to escrow secret material upon key creation
- Use centralized system policy for default algorithm selection
- Simplify System.Web compatibility layer
- Add unit tests, logging, and doc comments throughout solution
This commit is contained in:
Levi B 2015-03-10 22:17:31 -07:00
parent 04008af479
commit e8cc1106d8
239 changed files with 12770 additions and 3100 deletions

View File

@ -9,8 +9,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtec
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.Azure", "src\Microsoft.AspNet.DataProtection.Azure\Microsoft.AspNet.DataProtection.Azure.kproj", "{DF3671D7-A9B1-45F1-A195-0AD596001735}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.Compatibility", "src\Microsoft.AspNet.DataProtection.Compatibility\Microsoft.AspNet.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.DataProtection.Test", "test\Microsoft.AspNet.DataProtection.Test\Microsoft.AspNet.DataProtection.Test.kproj", "{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}"
@ -23,6 +21,16 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Cryptograp
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Cryptography.Internal.Test", "test\Microsoft.AspNet.Cryptography.Internal.Test\Microsoft.AspNet.Cryptography.Internal.Test.kproj", "{37053D5F-5B61-47CE-8B72-298CE007FFB0}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.Interfaces", "src\Microsoft.AspNet.DataProtection.Interfaces\Microsoft.AspNet.DataProtection.Interfaces.kproj", "{4B115BDE-B253-46A6-97BF-A8B37B344FF2}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.Interfaces.Test", "test\Microsoft.AspNet.DataProtection.Interfaces.Test\Microsoft.AspNet.DataProtection.Interfaces.Test.kproj", "{FF650A69-DEE4-4B36-9E30-264EE7CFB478}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.Test.Shared", "test\Microsoft.AspNet.DataProtection.Test.Shared\Microsoft.AspNet.DataProtection.Test.Shared.kproj", "{4F14BA2A-4F04-4676-8586-EC380977EE2E}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.Shared", "src\Microsoft.AspNet.DataProtection.Shared\Microsoft.AspNet.DataProtection.Shared.kproj", "{3277BB22-033F-4010-8131-A515B910CAAD}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.SystemWeb", "src\Microsoft.AspNet.DataProtection.SystemWeb\Microsoft.AspNet.DataProtection.SystemWeb.kproj", "{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -43,12 +51,6 @@ Global
{DF3671D7-A9B1-45F1-A195-0AD596001735}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF3671D7-A9B1-45F1-A195-0AD596001735}.Release|Any CPU.Build.0 = Release|Any CPU
{DF3671D7-A9B1-45F1-A195-0AD596001735}.Release|x86.ActiveCfg = Release|Any CPU
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}.Debug|x86.ActiveCfg = Debug|Any CPU
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}.Release|Any CPU.Build.0 = Release|Any CPU
{C2FD9D02-AA0E-45FA-8561-EE357A94B73D}.Release|x86.ActiveCfg = Release|Any CPU
{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|x86.ActiveCfg = Debug|Any CPU
@ -87,6 +89,46 @@ Global
{37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|Any CPU.Build.0 = Release|Any CPU
{37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|x86.ActiveCfg = Release|Any CPU
{37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|x86.Build.0 = Release|Any CPU
{4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|x86.ActiveCfg = Debug|Any CPU
{4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|x86.Build.0 = Debug|Any CPU
{4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|Any CPU.Build.0 = Release|Any CPU
{4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|x86.ActiveCfg = Release|Any CPU
{4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|x86.Build.0 = Release|Any CPU
{FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|x86.ActiveCfg = Debug|Any CPU
{FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|x86.Build.0 = Debug|Any CPU
{FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|Any CPU.Build.0 = Release|Any CPU
{FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|x86.ActiveCfg = Release|Any CPU
{FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|x86.Build.0 = Release|Any CPU
{4F14BA2A-4F04-4676-8586-EC380977EE2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F14BA2A-4F04-4676-8586-EC380977EE2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F14BA2A-4F04-4676-8586-EC380977EE2E}.Debug|x86.ActiveCfg = Debug|Any CPU
{4F14BA2A-4F04-4676-8586-EC380977EE2E}.Debug|x86.Build.0 = Debug|Any CPU
{4F14BA2A-4F04-4676-8586-EC380977EE2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F14BA2A-4F04-4676-8586-EC380977EE2E}.Release|Any CPU.Build.0 = Release|Any CPU
{4F14BA2A-4F04-4676-8586-EC380977EE2E}.Release|x86.ActiveCfg = Release|Any CPU
{4F14BA2A-4F04-4676-8586-EC380977EE2E}.Release|x86.Build.0 = Release|Any CPU
{3277BB22-033F-4010-8131-A515B910CAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3277BB22-033F-4010-8131-A515B910CAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3277BB22-033F-4010-8131-A515B910CAAD}.Debug|x86.ActiveCfg = Debug|Any CPU
{3277BB22-033F-4010-8131-A515B910CAAD}.Debug|x86.Build.0 = Debug|Any CPU
{3277BB22-033F-4010-8131-A515B910CAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3277BB22-033F-4010-8131-A515B910CAAD}.Release|Any CPU.Build.0 = Release|Any CPU
{3277BB22-033F-4010-8131-A515B910CAAD}.Release|x86.ActiveCfg = Release|Any CPU
{3277BB22-033F-4010-8131-A515B910CAAD}.Release|x86.Build.0 = Release|Any CPU
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|x86.ActiveCfg = Debug|Any CPU
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|x86.Build.0 = Debug|Any CPU
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|Any CPU.Build.0 = Release|Any CPU
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|x86.ActiveCfg = Release|Any CPU
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -94,11 +136,15 @@ Global
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}
{E2779976-A28C-4365-A4BB-4AD854FAF23E} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
{421F0383-34B1-402D-807B-A94542513ABA} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
{42C97F52-8D56-46BD-A712-4F22BED157A7} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
{37053D5F-5B61-47CE-8B72-298CE007FFB0} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
{4B115BDE-B253-46A6-97BF-A8B37B344FF2} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
{FF650A69-DEE4-4B36-9E30-264EE7CFB478} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
{4F14BA2A-4F04-4676-8586-EC380977EE2E} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
{3277BB22-033F-4010-8131-A515B910CAAD} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
EndGlobalSection
EndGlobal

View File

@ -1,3 +1,5 @@
use assembly='WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
use namespace='System.IO.Packaging'
var VERSION='0.1'
var FULL_VERSION='0.1'
@ -5,3 +7,39 @@ var AUTHORS='Microsoft Open Technologies, Inc.'
use-standard-lifecycle
k-standard-goals
#nupkg-patch target='compile'
@{
var packagePaths = Files.Include("artifacts/build/**/Microsoft.AspNet.DataProtection.SystemWeb.*.nupkg")
.Exclude("**/*.symbols.nupkg");
foreach (var packagePath in packagePaths)
{
using (var package = Package.Open(packagePath, FileMode.Open, FileAccess.ReadWrite))
{
CreatePartFromFile(
package,
@"src\Microsoft.AspNet.DataProtection.SystemWeb\web.config.transform",
@"content\web.config.transform");
}
}
}
functions
@{
PackagePart CreatePartFromFile(
Package destination,
string sourceFileName,
string partUriString)
{
var partUri = PackUriHelper.CreatePartUri(new Uri(partUriString, UriKind.Relative));
var packagePart = destination.CreatePart(partUri, "application/octet", CompressionOption.Maximum);
using (var sourceStream = File.OpenRead(sourceFileName))
using (var stream = packagePart.GetStream())
{
sourceStream.CopyTo(stream);
}
return packagePart;
}
}

View File

@ -2,7 +2,6 @@
// 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;
using Microsoft.AspNet.Cryptography.Internal;
@ -14,16 +13,16 @@ namespace Microsoft.AspNet.Cryptography.Cng
{
// 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;
internal uint dwMinLength;
internal uint dwMaxLength;
internal 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");
string message = Resources.FormatBCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength(keyLengthInBits, dwMinLength, dwMaxLength, dwIncrement);
throw new ArgumentOutOfRangeException(nameof(keyLengthInBits), message);
}
CryptoUtil.Assert(keyLengthInBits % 8 == 0, "keyLengthInBits % 8 == 0");
}

View File

@ -5,9 +5,14 @@ using System;
namespace Microsoft.AspNet.Cryptography.Cng
{
/// <summary>
/// Wraps utility BCRYPT APIs that don't work directly with handles.
/// </summary>
internal unsafe static class BCryptUtil
{
// helper function that's similar to RNGCryptoServiceProvider, but works directly with pointers
/// <summary>
/// Fills a buffer with cryptographically secure random data.
/// </summary>
public static void GenRandom(byte* pbBuffer, uint cbBuffer)
{
if (cbBuffer != 0)

View File

@ -23,86 +23,27 @@ namespace Microsoft.AspNet.Cryptography.Cng
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_CBC => CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesCbc);
public static BCryptAlgorithmHandle AES_GCM
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesGcm);
}
}
public static BCryptAlgorithmHandle AES_GCM => CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesGcm);
public static BCryptAlgorithmHandle HMAC_SHA1
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha1);
}
}
public static BCryptAlgorithmHandle HMAC_SHA1 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha1);
public static BCryptAlgorithmHandle HMAC_SHA256
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha256);
}
}
public static BCryptAlgorithmHandle HMAC_SHA256 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha256);
public static BCryptAlgorithmHandle HMAC_SHA512
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha512);
}
}
public static BCryptAlgorithmHandle HMAC_SHA512 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha512);
// Only available on Win8+.
public static BCryptAlgorithmHandle PBKDF2
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _pbkdf2);
}
}
public static BCryptAlgorithmHandle PBKDF2 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _pbkdf2);
public static BCryptAlgorithmHandle SHA1
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha1);
}
}
public static BCryptAlgorithmHandle SHA1 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha1);
public static BCryptAlgorithmHandle SHA256
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha256);
}
}
public static BCryptAlgorithmHandle SHA256 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha256);
public static BCryptAlgorithmHandle SHA512
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha512);
}
}
public static BCryptAlgorithmHandle SHA512 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha512);
public static BCryptAlgorithmHandle SP800_108_CTR_HMAC
{
get
{
return CachedAlgorithmInfo.GetAlgorithmHandle(ref _sp800_108_ctr_hmac);
}
}
// Only available on Win8+.
public static BCryptAlgorithmHandle SP800_108_CTR_HMAC => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sp800_108_ctr_hmac);
private static BCryptAlgorithmHandle GetAesAlgorithm(string chainingMode)
{

View File

@ -46,12 +46,12 @@ namespace Microsoft.AspNet.Cryptography.Cng
}
}
public static bool IsBCryptOnWin7OrLaterAvailable()
public static bool IsWindows()
{
return (_osVersion >= OSVersion.Win7OrLater);
}
public static bool IsBCryptOnWin8OrLaterAvailable()
public static bool IsWindows8OrLater()
{
return (_osVersion >= OSVersion.Win8OrLater);
}

View File

@ -6,6 +6,8 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Microsoft.AspNet.Cryptography.Cng;
using Microsoft.AspNet.Cryptography.Internal;
#if !DNXCORE50
using System.Runtime.ConstrainedExecution;
@ -32,6 +34,24 @@ namespace Microsoft.AspNet.Cryptography
Assert(safeHandle != null && !safeHandle.IsInvalid, "Safe handle is invalid.");
}
// Asserts that the current platform is Windows; throws PlatformNotSupportedException otherwise.
public static void AssertPlatformIsWindows()
{
if (!OSVersionUtil.IsWindows())
{
throw new PlatformNotSupportedException(Resources.Platform_Windows7Required);
}
}
// Asserts that the current platform is Windows 8 or above; throws PlatformNotSupportedException otherwise.
public static void AssertPlatformIsWindows8OrLater()
{
if (!OSVersionUtil.IsWindows8OrLater())
{
throw new PlatformNotSupportedException(Resources.Platform_Windows8Required);
}
}
// 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

View File

@ -12,4 +12,5 @@ using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("Microsoft.AspNet.Cryptography.KeyDerivation")]
[assembly: InternalsVisibleTo("Microsoft.AspNet.Cryptography.KeyDerivation.Test")]
[assembly: InternalsVisibleTo("Microsoft.AspNet.DataProtection")]
[assembly: InternalsVisibleTo("Microsoft.AspNet.DataProtection.Interfaces.Test")]
[assembly: InternalsVisibleTo("Microsoft.AspNet.DataProtection.Test")]

View File

@ -42,6 +42,38 @@ namespace Microsoft.AspNet.Cryptography.Internal
return string.Format(CultureInfo.CurrentCulture, GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength"), p0, p1, p2, p3);
}
/// <summary>
/// This operation requires Windows 7 / Windows Server 2008 R2 or later.
/// </summary>
internal static string Platform_Windows7Required
{
get { return GetString("Platform_Windows7Required"); }
}
/// <summary>
/// This operation requires Windows 7 / Windows Server 2008 R2 or later.
/// </summary>
internal static string FormatPlatform_Windows7Required()
{
return GetString("Platform_Windows7Required");
}
/// <summary>
/// This operation requires Windows 8 / Windows Server 2012 or later.
/// </summary>
internal static string Platform_Windows8Required
{
get { return GetString("Platform_Windows8Required"); }
}
/// <summary>
/// This operation requires Windows 8 / Windows Server 2012 or later.
/// </summary>
internal static string FormatPlatform_Windows8Required()
{
return GetString("Platform_Windows8Required");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -123,4 +123,10 @@
<data name="BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength" xml:space="preserve">
<value>The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).</value>
</data>
<data name="Platform_Windows7Required" xml:space="preserve">
<value>This operation requires Windows 7 / Windows Server 2008 R2 or later.</value>
</data>
<data name="Platform_Windows8Required" xml:space="preserve">
<value>This operation requires Windows 8 / Windows Server 2012 or later.</value>
</data>
</root>

View File

@ -10,6 +10,9 @@ using Microsoft.AspNet.Cryptography.Internal;
namespace Microsoft.AspNet.Cryptography.SafeHandles
{
/// <summary>
/// Represents a handle to a BCrypt algorithm provider from which keys and hashes can be created.
/// </summary>
internal unsafe sealed class BCryptAlgorithmHandle : BCryptHandle
{
// Called by P/Invoke when returning SafeHandles
@ -20,10 +23,10 @@ namespace Microsoft.AspNet.Cryptography.SafeHandles
/// </summary>
public BCryptHashHandle CreateHash()
{
return CreateHashImpl(null, 0);
return CreateHashCore(null, 0);
}
private BCryptHashHandle CreateHashImpl(byte* pbKey, uint cbKey)
private BCryptHashHandle CreateHashCore(byte* pbKey, uint cbKey)
{
BCryptHashHandle retVal;
int ntstatus = UnsafeNativeMethods.BCryptCreateHash(this, out retVal, IntPtr.Zero, 0, pbKey, cbKey, dwFlags: 0);
@ -40,7 +43,7 @@ namespace Microsoft.AspNet.Cryptography.SafeHandles
public BCryptHashHandle CreateHmac(byte* pbKey, uint cbKey)
{
Debug.Assert(pbKey != null);
return CreateHashImpl(pbKey, cbKey);
return CreateHashCore(pbKey, cbKey);
}
/// <summary>

View File

@ -6,13 +6,33 @@ using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNet.Cryptography.SafeHandles
{
internal sealed class NCryptDescriptorHandle : SafeHandleZeroOrMinusOneIsInvalid
internal unsafe sealed class NCryptDescriptorHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private NCryptDescriptorHandle()
: base(ownsHandle: true)
{
}
public string GetProtectionDescriptorRuleString()
{
// from ncryptprotect.h
const int NCRYPT_PROTECTION_INFO_TYPE_DESCRIPTOR_STRING = 0x00000001;
LocalAllocHandle ruleStringHandle;
int ntstatus = UnsafeNativeMethods.NCryptGetProtectionDescriptorInfo(
hDescriptor: this,
pMemPara: IntPtr.Zero,
dwInfoType: NCRYPT_PROTECTION_INFO_TYPE_DESCRIPTOR_STRING,
ppvInfo: out ruleStringHandle);
UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus);
CryptoUtil.AssertSafeHandleIsValid(ruleStringHandle);
using (ruleStringHandle)
{
return new String((char*)ruleStringHandle.DangerousGetHandle());
}
}
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{

View File

@ -1,30 +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 Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNet.Cryptography.SafeHandles
{
internal sealed class SafeCertContextHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeCertContextHandle()
: base(ownsHandle: true)
{
}
public static SafeCertContextHandle CreateDuplicateFrom(IntPtr existingHandle)
{
SafeCertContextHandle newHandle = UnsafeNativeMethods.CertDuplicateCertificateContext(existingHandle);
CryptoUtil.AssertSafeHandleIsValid(newHandle);
return newHandle;
}
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
return UnsafeNativeMethods.CertFreeCertificateContext(handle);
}
}
}

View File

@ -1,28 +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.InteropServices;
using Microsoft.Win32.SafeHandles;
#if DNXCORE50
namespace Microsoft.AspNet.Cryptography.SafeHandles
{
/// <summary>
/// Represents a managed view over an NCRYPT_KEY_HANDLE.
/// </summary>
internal class SafeNCryptKeyHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// Called by P/Invoke when returning SafeHandles
protected SafeNCryptKeyHandle()
: base(ownsHandle: true) { }
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
// TODO: Replace me with a real implementation on CoreClr.
throw new NotImplementedException();
}
}
}
#endif

View File

@ -2,9 +2,7 @@
// 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;
using System.Security;
#if !DNXCORE50
using System.Runtime.ConstrainedExecution;

View File

@ -14,8 +14,6 @@ namespace Microsoft.AspNet.Cryptography
{
internal unsafe static class UnsafeBufferUtil
{
private static readonly byte[] _emptyArray = new byte[0];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if !DNXCORE50
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
@ -33,7 +31,7 @@ namespace Microsoft.AspNet.Cryptography
{
if (byteCount != 0)
{
BlockCopyImpl((byte*)from, (byte*)to, byteCount);
BlockCopyCore((byte*)from, (byte*)to, byteCount);
}
}
@ -60,7 +58,7 @@ namespace Microsoft.AspNet.Cryptography
#if !DNXCORE50
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
#endif
public static void BlockCopy(byte* from, LocalAllocHandle to, uint byteCount)
public static void BlockCopy(void* from, LocalAllocHandle to, uint byteCount)
{
bool refAdded = false;
try
@ -95,10 +93,11 @@ namespace Microsoft.AspNet.Cryptography
to.DangerousAddRef(ref toRefAdded);
if (sizeof(IntPtr) == 4)
{
BlockCopyImpl(from: (byte*)from.DangerousGetHandle(), to: (byte*)to.DangerousGetHandle(), byteCount: (uint)length.ToInt32());
} else
BlockCopyCore(from: (byte*)from.DangerousGetHandle(), to: (byte*)to.DangerousGetHandle(), byteCount: (uint)length.ToInt32());
}
else
{
BlockCopyImpl(from: (byte*)from.DangerousGetHandle(), to: (byte*)to.DangerousGetHandle(), byteCount: (ulong)length.ToInt64());
BlockCopyCore(from: (byte*)from.DangerousGetHandle(), to: (byte*)to.DangerousGetHandle(), byteCount: (ulong)length.ToInt64());
}
}
finally
@ -115,24 +114,26 @@ namespace Microsoft.AspNet.Cryptography
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void BlockCopyImpl(byte* from, byte* to, uint byteCount)
private static void BlockCopyCore(byte* from, byte* to, uint byteCount)
{
#if DNXCORE50
Buffer.MemoryCopy(from, to, (ulong)byteCount, (ulong)byteCount);
#else
while (byteCount-- != 0) {
while (byteCount-- != 0)
{
to[byteCount] = from[byteCount];
}
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void BlockCopyImpl(byte* from, byte* to, ulong byteCount)
private static void BlockCopyCore(byte* from, byte* to, ulong byteCount)
{
#if DNXCORE50
Buffer.MemoryCopy(from, to, byteCount, byteCount);
#else
while (byteCount-- != 0) {
while (byteCount-- != 0)
{
to[byteCount] = from[byteCount];
}
#endif
@ -209,33 +210,5 @@ namespace Microsoft.AspNet.Cryptography
SecureZeroMemory(buffer, (ulong)length.ToInt64());
}
}
/// <summary>
/// Creates a new managed byte[] from unmanaged memory.
/// </summary>
public static byte[] ToManagedByteArray(byte* ptr, int byteCount)
{
return ToManagedByteArray(ptr, checked((uint)byteCount));
}
/// <summary>
/// Creates a new managed byte[] from unmanaged memory.
/// </summary>
public static byte[] ToManagedByteArray(byte* ptr, uint byteCount)
{
if (byteCount == 0)
{
return _emptyArray; // degenerate case
}
else
{
byte[] bytes = new byte[byteCount];
fixed (byte* pBytes = bytes)
{
BlockCopy(from: ptr, to: pBytes, byteCount: byteCount);
}
return bytes;
}
}
}
}

View File

@ -200,44 +200,7 @@ namespace Microsoft.AspNet.Cryptography
/*
* CRYPT32.DLL
*/
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi)]
#if !DNXCORE50
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
#endif
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa376045(v=vs.85).aspx
internal static extern SafeCertContextHandle CertDuplicateCertificateContext(
[In] IntPtr pCertContext);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi)]
#if !DNXCORE50
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
#endif
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa376075(v=vs.85).aspx
internal static extern bool CertFreeCertificateContext(
[In] IntPtr pCertContext);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa376079(v=vs.85).aspx
internal static extern bool CertGetCertificateContextProperty(
[In] SafeCertContextHandle pCertContext,
[In] uint dwPropId,
[In] void* pvData,
[In, Out] ref uint pcbData);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa379885(v=vs.85).aspx
#if !DNXCORE50
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
#endif
internal static extern bool CryptAcquireCertificatePrivateKey(
[In] SafeCertContextHandle pCert,
[In] uint dwFlags,
[In] void* pvParameters,
[Out] out SafeNCryptKeyHandle phCryptProvOrNCryptKey,
[Out] out uint pdwKeySpec,
[Out] out bool pfCallerFreeProvOrNCryptKey);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261(v=vs.85).aspx
internal static extern bool CryptProtectData(
@ -301,16 +264,12 @@ namespace Microsoft.AspNet.Cryptography
[Out] out NCryptDescriptorHandle phDescriptor);
[DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa376249(v=vs.85).aspx
internal static extern int NCryptDecrypt(
[In] SafeNCryptKeyHandle hKey,
[In] byte* pbInput,
[In] uint cbInput,
[In] void* pPaddingInfo,
[In] byte* pbOutput,
[In] uint cbOutput,
[Out] out uint pcbResult,
[In] NCryptEncryptFlags dwFlags);
// https://msdn.microsoft.com/en-us/library/windows/desktop/hh706801(v=vs.85).aspx
internal static extern int NCryptGetProtectionDescriptorInfo(
[In] NCryptDescriptorHandle hDescriptor,
[In] IntPtr pMemPara,
[In] uint dwInfoType,
[Out] out LocalAllocHandle ppvInfo);
[DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/hh706802(v=vs.85).aspx
@ -336,6 +295,18 @@ namespace Microsoft.AspNet.Cryptography
[Out] out LocalAllocHandle ppbData,
[Out] out uint pcbData);
[DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/hh706811(v=vs.85).aspx
internal static extern int NCryptUnprotectSecret(
[Out] out NCryptDescriptorHandle phDescriptor,
[In] uint dwFlags,
[In] byte* pbProtectedBlob,
[In] uint cbProtectedBlob,
[In] IntPtr pMemPara,
[In] IntPtr hWnd,
[Out] out LocalAllocHandle ppbData,
[Out] out uint pcbData);
/*
* HELPER FUNCTIONS
*/

View File

@ -6,8 +6,24 @@ using Microsoft.AspNet.Cryptography.KeyDerivation.PBKDF2;
namespace Microsoft.AspNet.Cryptography.KeyDerivation
{
/// <summary>
/// Provides algorithms for performing key derivation.
/// </summary>
public static class KeyDerivation
{
/// <summary>
/// Performs key derivation using the PBKDF2 algorithm.
/// </summary>
/// <param name="password">The password from which to derive the key.</param>
/// <param name="salt">The salt to be used during the key derivation process.</param>
/// <param name="prf">The pseudo-random function to be used in the key derivation process.</param>
/// <param name="iterationCount">The number of iterations of the pseudo-random function to apply
/// during the key derivation process.</param>
/// <param name="numBytesRequested">The desired length (in bytes) of the derived key.</param>
/// <returns>The derived key.</returns>
/// <remarks>
/// The PBKDF2 algorithm is specified in RFC 2898.
/// </remarks>
public static byte[] Pbkdf2(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
{
// parameter checking

View File

@ -16,11 +16,11 @@ namespace Microsoft.AspNet.Cryptography.KeyDerivation.PBKDF2
private static IPbkdf2Provider GetPbkdf2Provider()
{
// In priority order, our three implementations are Win8, Win7, and "other".
if (OSVersionUtil.IsBCryptOnWin8OrLaterAvailable())
if (OSVersionUtil.IsWindows8OrLater())
{
// fastest implementation
return new Win8Pbkdf2Provider();
} else if (OSVersionUtil.IsBCryptOnWin7OrLaterAvailable())
} else if (OSVersionUtil.IsWindows())
{
// acceptable implementation
return new Win7Pbkdf2Provider();

View File

@ -9,6 +9,7 @@ using System.Net;
using System.Runtime.ExceptionServices;
using System.Xml.Linq;
using Microsoft.AspNet.DataProtection.Repositories;
using Microsoft.Framework.Internal;
using Microsoft.Framework.OptionsModel;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
@ -49,7 +50,7 @@ namespace Microsoft.AspNet.DataProtection.Azure
{
var blobRef = GetKeyRingBlockBlobReference();
XDocument document = ReadDocumentFromStorage(blobRef);
return document?.Root.Elements().ToArray() ?? new XElement[0];
return (IReadOnlyCollection<XElement>)document?.Root.Elements().ToList().AsReadOnly() ?? new XElement[0];
}
private XDocument ReadDocumentFromStorage(CloudBlockBlob blobRef)

View File

@ -1,12 +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.DataProtection.Azure
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
internal sealed class NotNullAttribute : Attribute
{
}
}

View File

@ -3,6 +3,7 @@
"description": "ASP.NET 5 blob storage repository for DataProtection.",
"dependencies": {
"Microsoft.AspNet.DataProtection": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" },
"WindowsAzure.Storage": "4.3.0"
},
"frameworks": {

View File

@ -1,49 +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.Threading;
namespace Microsoft.AspNet.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

@ -1,72 +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;
namespace Microsoft.AspNet.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

@ -1,49 +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.Threading;
namespace Microsoft.AspNet.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

@ -1,14 +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.DataProtection.Compatibility
{
internal interface IFactorySupportFunctions
{
IDataProtectionProvider CreateDataProtectionProvider();
IDataProtector CreateDataProtector(IDataProtectionProvider dataProtectionProvider);
}
}

View File

@ -0,0 +1,33 @@
// 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.DataProtection
{
internal static class CryptoUtil
{
// 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);
}
// Allows callers to write "var x = Method() ?? Fail<T>(message);" as a convenience to guard
// against a method returning null unexpectedly.
[MethodImpl(MethodImplOptions.NoInlining)]
public static T Fail<T>(string message) where T : class
{
throw Fail(message);
}
}
}

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 System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNet.DataProtection.Interfaces;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection
{
/// <summary>
/// Helpful extension methods for data protection APIs.
/// </summary>
public static class DataProtectionExtensions
{
/// <summary>
/// Creates an <see cref="IDataProtector"/> given a list of purposes.
/// </summary>
/// <param name="provider">The <see cref="IDataProtectionProvider"/> from which to generate the purpose chain.</param>
/// <param name="purposes">The list of purposes which contribute to the purpose chain. This list must
/// contain at least one element, and it may not contain null elements.</param>
/// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
/// <remarks>
/// This is a convenience method which chains together several calls to
/// <see cref="IDataProtectionProvider.CreateProtector(string)"/>. See that method's
/// documentation for more information.
/// </remarks>
public static IDataProtector CreateProtector([NotNull] this IDataProtectionProvider provider, [NotNull] IEnumerable<string> purposes)
{
bool collectionIsEmpty = true;
IDataProtectionProvider retVal = provider;
foreach (string purpose in purposes)
{
if (purpose == null)
{
throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesCollection, nameof(purposes));
}
retVal = retVal.CreateProtector(purpose) ?? CryptoUtil.Fail<IDataProtector>("CreateProtector returned null.");
collectionIsEmpty = false;
}
if (collectionIsEmpty)
{
throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesCollection, nameof(purposes));
}
Debug.Assert(retVal is IDataProtector); // CreateProtector is supposed to return an instance of this interface
return (IDataProtector)retVal;
}
/// <summary>
/// Creates an <see cref="IDataProtector"/> given a list of purposes.
/// </summary>
/// <param name="provider">The <see cref="IDataProtectionProvider"/> from which to generate the purpose chain.</param>
/// <param name="purpose">The primary purpose used to create the <see cref="IDataProtectionProvider"/>.</param>
/// <param name="subPurposes">An optional list of secondary purposes which contribute to the purpose chain.
/// If this list is provided it cannot contain null elements.</param>
/// <returns>An <see cref="IDataProtector"/> tied to the provided purpose chain.</returns>
/// <remarks>
/// This is a convenience method which chains together several calls to
/// <see cref="IDataProtectionProvider.CreateProtector(string)"/>. See that method's
/// documentation for more information.
/// </remarks>
public static IDataProtector CreateProtector([NotNull] this IDataProtectionProvider provider, [NotNull] string purpose, params string[] subPurposes)
{
// The method signature isn't simply CreateProtector(this IDataProtectionProvider, params string[] purposes)
// because we don't want the code provider.CreateProtector() [parameterless] to inadvertently compile.
// The actual signature for this method forces at least one purpose to be provided at the call site.
IDataProtector protector = provider.CreateProtector(purpose);
if (subPurposes != null && subPurposes.Length > 0)
{
protector = protector?.CreateProtector((IEnumerable<string>)subPurposes);
}
return protector ?? CryptoUtil.Fail<IDataProtector>("CreateProtector returned null.");
}
/// <summary>
/// Cryptographically protects a piece of plaintext data.
/// </summary>
/// <param name="protector">The data protector to use for this operation.</param>
/// <param name="plaintext">The plaintext data to protect.</param>
/// <returns>The protected form of the plaintext data.</returns>
public static string Protect([NotNull] this IDataProtector protector, [NotNull] string plaintext)
{
try
{
byte[] plaintextAsBytes = EncodingUtil.SecureUtf8Encoding.GetBytes(plaintext);
byte[] protectedDataAsBytes = protector.Protect(plaintextAsBytes);
return WebEncoders.Base64UrlEncode(protectedDataAsBytes);
}
catch (Exception ex) when (ex.RequiresHomogenization())
{
// Homogenize exceptions to CryptographicException
throw Error.CryptCommon_GenericError(ex);
}
}
/// <summary>
/// Cryptographically unprotects a piece of protected data.
/// </summary>
/// <param name="protector">The data protector to use for this operation.</param>
/// <param name="protectedData">The protected data to unprotect.</param>
/// <returns>The plaintext form of the protected data.</returns>
/// <remarks>
/// This method will throw CryptographicException if the input is invalid or malformed.
/// </remarks>
public static string Unprotect([NotNull] this IDataProtector protector, [NotNull] string protectedData)
{
try
{
byte[] protectedDataAsBytes = WebEncoders.Base64UrlDecode(protectedData);
byte[] plaintextAsBytes = protector.Unprotect(protectedDataAsBytes);
return EncodingUtil.SecureUtf8Encoding.GetString(plaintextAsBytes);
}
catch (Exception ex) when (ex.RequiresHomogenization())
{
// Homogenize exceptions to CryptographicException
throw Error.CryptCommon_GenericError(ex);
}
}
}
}

View File

@ -0,0 +1,23 @@
// 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.DataProtection.Interfaces;
namespace Microsoft.AspNet.DataProtection
{
internal static class Error
{
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);
}
}
}

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;
namespace Microsoft.AspNet.DataProtection
{
/// <summary>
/// Provides information used to discriminate applications.
/// </summary>
public interface IApplicationDiscriminator
{
/// <summary>
/// An identifier that uniquely discriminates this application from all other
/// applications on the machine.
/// </summary>
string Discriminator { get; }
}
}

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 Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection
{
/// <summary>
/// An interface that can be used to create <see cref="IDataProtector"/> instances.
/// </summary>
public interface IDataProtectionProvider
{
/// <summary>
/// Creates an <see cref="IDataProtector"/> given a purpose.
/// </summary>
/// <param name="purposes">
/// The purpose to be assigned to the newly-created <see cref="IDataProtector"/>.
/// </param>
/// <returns>An IDataProtector tied to the provided purpose.</returns>
/// <remarks>
/// The <paramref name="purpose"/> parameter must be unique for the intended use case; two
/// different <see cref="IDataProtector"/> instances created with two different <paramref name="purpose"/>
/// values will not be able to decipher each other's payloads. The <paramref name="purpose"/> parameter
/// value is not intended to be kept secret.
/// </remarks>
IDataProtector CreateProtector([NotNull] string purpose);
}
}

View File

@ -2,6 +2,8 @@
// 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.Framework.Internal;
namespace Microsoft.AspNet.DataProtection
{
@ -13,19 +15,18 @@ namespace Microsoft.AspNet.DataProtection
/// <summary>
/// Cryptographically protects a piece of plaintext data.
/// </summary>
/// <param name="unprotectedData">The plaintext data to protect.</param>
/// <param name="plaintext">The plaintext data to protect.</param>
/// <returns>The protected form of the plaintext data.</returns>
byte[] Protect(byte[] unprotectedData);
byte[] Protect([NotNull] byte[] plaintext);
/// <summary>
/// Cryptographically unprotects a piece of protected data.
/// </summary>
/// <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);
/// <exception cref="CryptographicException">
/// Thrown if the protected data is invalid or malformed.
/// </exception>
byte[] Unprotect([NotNull] byte[] protectedData);
}
}

View File

@ -6,7 +6,7 @@
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>C2FD9D02-AA0E-45FA-8561-EE357A94B73D</ProjectGuid>
<ProjectGuid>4b115bde-b253-46a6-97bf-a8b37b344ff2</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>

View File

@ -2,11 +2,7 @@
// 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.DataProtection.Compatibility
{
public interface IDataProtectionProviderFactory
{
IDataProtectionProvider CreateDataProtectionProvider();
}
}
// for unit testing
[assembly: InternalsVisibleTo("Microsoft.AspNet.DataProtection.Interfaces.Test")]

View File

@ -0,0 +1,78 @@
// <auto-generated />
namespace Microsoft.AspNet.DataProtection.Interfaces
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.DataProtection.Interfaces.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The payload was invalid.
/// </summary>
internal static string CryptCommon_PayloadInvalid
{
get { return GetString("CryptCommon_PayloadInvalid"); }
}
/// <summary>
/// The payload was invalid.
/// </summary>
internal static string FormatCryptCommon_PayloadInvalid()
{
return GetString("CryptCommon_PayloadInvalid");
}
/// <summary>
/// The purposes collection cannot be null or empty and cannot contain null elements.
/// </summary>
internal static string DataProtectionExtensions_NullPurposesCollection
{
get { return GetString("DataProtectionExtensions_NullPurposesCollection"); }
}
/// <summary>
/// The purposes collection cannot be null or empty and cannot contain null elements.
/// </summary>
internal static string FormatDataProtectionExtensions_NullPurposesCollection()
{
return GetString("DataProtectionExtensions_NullPurposesCollection");
}
/// <summary>
/// An error occurred during a cryptographic operation.
/// </summary>
internal static string CryptCommon_GenericError
{
get { return GetString("CryptCommon_GenericError"); }
}
/// <summary>
/// An error occurred during a cryptographic operation.
/// </summary>
internal static string FormatCryptCommon_GenericError()
{
return GetString("CryptCommon_GenericError");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CryptCommon_PayloadInvalid" xml:space="preserve">
<value>The payload was invalid.</value>
</data>
<data name="DataProtectionExtensions_NullPurposesCollection" xml:space="preserve">
<value>The purposes collection cannot be null or empty and cannot contain null elements.</value>
</data>
<data name="CryptCommon_GenericError" xml:space="preserve">
<value>An error occurred during a cryptographic operation.</value>
</data>
</root>

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNet.DataProtection
/// The input must not contain any whitespace or padding characters.
/// Throws FormatException if the input is malformed.
/// </remarks>
public static byte[] Base64UrlDecode([NotNull] string input)
public static byte[] Base64UrlDecode(string input)
{
// Assumption: input is base64url encoded without padding and contains no whitespace.
@ -56,7 +56,7 @@ namespace Microsoft.AspNet.DataProtection
/// </summary>
/// <param name="input">The binary input to encode.</param>
/// <returns>The base64url-encoded form of the input.</returns>
public static string Base64UrlEncode([NotNull] byte[] input)
public static string Base64UrlEncode(byte[] input)
{
// Special-case empty input
if (input.Length == 0)
@ -126,7 +126,7 @@ namespace Microsoft.AspNet.DataProtection
case 3:
return 1;
default:
throw new FormatException("TODO: Malformed input.");
throw Error.CryptCommon_PayloadInvalid(); // not valid base64
}
}
}

View File

@ -0,0 +1,25 @@
{
"version": "1.0.0-*",
"description": "Contains the core IDataProtector and IDataProtectionProvider interfaces for ASP.NET 5 Data Protection.",
"dependencies": {
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" },
"Microsoft.AspNet.DataProtection.Shared": { "type": "build", "version": "" }
},
"frameworks": {
"net451": { },
"dnx451": { },
"dnxcore50": {
"dependencies": {
"System.Diagnostics.Debug": "4.0.10-beta-*",
"System.Reflection": "4.0.10-beta-*",
"System.Resources.ResourceManager": "4.0.0-beta-*",
"System.Runtime.Extensions": "4.0.10-beta-*",
"System.Security.Cryptography.Encryption": "4.0.0-beta-*",
"System.Text.Encoding.Extensions": "4.0.10-beta-*"
}
}
},
"compilationOptions": {
"warningsAsErrors": true
}
}

View File

@ -6,7 +6,7 @@ using System.Text;
namespace Microsoft.AspNet.DataProtection
{
internal unsafe static class EncodingUtil
internal static class EncodingUtil
{
// UTF8 encoding that fails on invalid chars
public static readonly UTF8Encoding SecureUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.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>3277bb22-033f-4010-8131-a515b910caad</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,20 @@
{
"version": "1.0.0-*",
"description": "ASP.NET 5 Data Protection shared code.",
"dependencies": {
},
"frameworks": {
"net451": { },
"dnx451": { },
"dnxcore50": {
"dependencies": {
"System.Security.Cryptography.Encryption": "4.0.0-beta-*",
"System.Text.Encoding.Extensions": "4.0.10-beta-*"
}
}
},
"shared": "**\\*.cs",
"compilationOptions": {
"warningsAsErrors": true
}
}

View File

@ -0,0 +1,82 @@
// 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.ComponentModel;
using System.Configuration;
using System.Security.Cryptography;
namespace Microsoft.AspNet.DataProtection.SystemWeb
{
/// <summary>
/// A <see cref="DataProtector"/> that can be used by ASP.NET 4.x to interact with ASP.NET 5's
/// DataProtection stack. This type is for internal use only and shouldn't be directly used by
/// developers.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class CompatibilityDataProtector : DataProtector
{
private static readonly Lazy<IDataProtectionProvider> _lazyProtectionProvider = new Lazy<IDataProtectionProvider>(CreateProtectionProvider);
private readonly Lazy<IDataProtector> _lazyProtector;
public CompatibilityDataProtector(string applicationName, string primaryPurpose, string[] specificPurposes)
: base("application-name", "primary-purpose", null) // we feed dummy values to the base ctor
{
// We don't want to evaluate the IDataProtectionProvider factory quite yet,
// as we'd rather defer failures to the call to Protect so that we can bubble
// up a good error message to the developer.
_lazyProtector = new Lazy<IDataProtector>(() => _lazyProtectionProvider.Value.CreateProtector(primaryPurpose, specificPurposes));
}
// We take care of flowing purposes ourselves.
protected override bool PrependHashedPurposeToPlaintext { get; } = false;
private static IDataProtectionProvider CreateProtectionProvider()
{
// Read from <appSettings> the startup type we need to use, then create it
const string APPSETTINGS_KEY = "aspnet:dataProtectionStartupType";
string startupTypeName = ConfigurationManager.AppSettings[APPSETTINGS_KEY];
if (String.IsNullOrEmpty(startupTypeName))
{
// fall back to default startup type if one hasn't been specified in config
startupTypeName = typeof(DataProtectionStartup).AssemblyQualifiedName;
}
Type startupType = Type.GetType(startupTypeName, throwOnError: true);
var startupInstance = (DataProtectionStartup)Activator.CreateInstance(startupType);
// Use it to initialize the system.
return startupInstance.InternalConfigureServicesAndCreateProtectionProvider();
}
public override bool IsReprotectRequired(byte[] encryptedData)
{
// Nobody ever calls this.
return false;
}
protected override byte[] ProviderProtect(byte[] userData)
{
try
{
return _lazyProtector.Value.Protect(userData);
}
catch (Exception ex)
{
// System.Web special-cases ConfigurationException errors and allows them to bubble
// up to the developer without being homogenized. Since a call to Protect should
// never fail, any exceptions here really do imply a misconfiguration.
#pragma warning disable CS0618 // Type or member is obsolete
throw new ConfigurationException(Resources.DataProtector_ProtectFailed, ex);
#pragma warning restore CS0618 // Type or member is obsolete
}
}
protected override byte[] ProviderUnprotect(byte[] encryptedData)
{
return _lazyProtector.Value.Unprotect(encryptedData);
}
}
}

View File

@ -0,0 +1,94 @@
// 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.Configuration;
using System.Web;
using System.Web.Configuration;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.DataProtection.SystemWeb
{
/// <summary>
/// Allows controlling the configuration of the ASP.NET 5 Data Protection system.
/// </summary>
/// <remarks>
/// Developers should not call these APIs directly. Instead, developers should subclass
/// this type and override the <see cref="ConfigureServices(IServiceCollection)"/>
/// method or <see cref="CreateDataProtectionProvider(IServiceProvider)"/> methods
/// as appropriate.
/// </remarks>
public class DataProtectionStartup
{
/// <summary>
/// Configures services used by the Data Protection system.
/// </summary>
/// <param name="services">A mutable collection of services.</param>
/// <remarks>
/// Developers may override this method to change the default behaviors of
/// the Data Protection system.
/// </remarks>
public virtual void ConfigureServices(IServiceCollection services)
{
// InternalConfigureServices already takes care of default configuration.
// The reason we don't configure default logic in this method is that we don't
// want to punish the developer for forgetting to call base.ConfigureServices
// from within his own override.
}
/// <summary>
/// Creates a new instance of an <see cref="IDataProtectionProvider"/>.
/// </summary>
/// <param name="services">A collection of services from which to create the <see cref="IDataProtectionProvider"/>.</param>
/// <returns>An <see cref="IDataProtectionProvider"/>.</returns>
/// <remarks>
/// Developers should generally override the <see cref="ConfigureServices(IServiceCollection)"/>
/// method instead of this method.
/// </remarks>
public virtual IDataProtectionProvider CreateDataProtectionProvider(IServiceProvider services)
{
return services.GetRequiredService<IDataProtectionProvider>();
}
/// <summary>
/// Provides a default implementation of required services, calls the developer's
/// configuration overrides, then creates an <see cref="IDataProtectionProvider"/>.
/// </summary>
internal IDataProtectionProvider InternalConfigureServicesAndCreateProtectionProvider()
{
var services = new ServiceCollection();
services.AddDataProtection();
services.Configure<DataProtectionOptions>(options =>
{
// Try reading the discriminator from <machineKey applicationName="..." /> defined
// at the web app root. If the value was set explicitly (even if the value is empty),
// honor it as the discriminator. Otherwise, fall back to the metabase config path.
var machineKeySection = (MachineKeySection)WebConfigurationManager.GetWebApplicationSection("system.web/machineKey");
if (machineKeySection.ElementInformation.Properties["applicationName"].ValueOrigin != PropertyValueOrigin.Default)
{
options.ApplicationDiscriminator = machineKeySection.ApplicationName;
}
else
{
options.ApplicationDiscriminator = HttpRuntime.AppDomainAppId;
}
if (String.IsNullOrEmpty(options.ApplicationDiscriminator))
{
options.ApplicationDiscriminator = null; // homogenize to null
}
});
// Run configuration and get an instance of the provider.
ConfigureServices(services);
var provider = CreateDataProtectionProvider(services.BuildServiceProvider());
if (provider == null)
{
throw new InvalidOperationException(Resources.Startup_CreateProviderReturnedNull);
}
// And we're done!
return provider;
}
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.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>e3552deb-4173-43ae-bf69-3c10dff3bab6</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,62 @@
// <auto-generated />
namespace Microsoft.AspNet.DataProtection.SystemWeb
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.DataProtection.SystemWeb.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// A call to Protect failed. This most likely means that the data protection system is misconfigured. See the inner exception for more information.
/// </summary>
internal static string DataProtector_ProtectFailed
{
get { return GetString("DataProtector_ProtectFailed"); }
}
/// <summary>
/// A call to Protect failed. This most likely means that the data protection system is misconfigured. See the inner exception for more information.
/// </summary>
internal static string FormatDataProtector_ProtectFailed()
{
return GetString("DataProtector_ProtectFailed");
}
/// <summary>
/// The CreateDataProtectionProvider method returned null.
/// </summary>
internal static string Startup_CreateProviderReturnedNull
{
get { return GetString("Startup_CreateProviderReturnedNull"); }
}
/// <summary>
/// The CreateDataProtectionProvider method returned null.
/// </summary>
internal static string FormatStartup_CreateProviderReturnedNull()
{
return GetString("Startup_CreateProviderReturnedNull");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DataProtector_ProtectFailed" xml:space="preserve">
<value>A call to Protect failed. This most likely means that the data protection system is misconfigured. See the inner exception for more information.</value>
</data>
<data name="Startup_CreateProviderReturnedNull" xml:space="preserve">
<value>The CreateDataProtectionProvider method returned null.</value>
</data>
</root>

View File

@ -4,10 +4,13 @@
"frameworks": {
"net451": {
"dependencies": {
"Microsoft.AspNet.DataProtection": "1.0.0-*"
"Microsoft.AspNet.DataProtection": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*"
},
"frameworkAssemblies": {
"System.Security": "4.0.0.0"
"System.Configuration": "4.0.0.0",
"System.Security": "4.0.0.0",
"System.Web": "4.0.0.0"
}
}
},

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--
If you want to customize the behavior of the ASP.NET 5 Data Protection stack, set the
"aspnet:dataProtectionStartupType" switch below to be the fully-qualified name of a
type which subclasses Microsoft.AspNet.DataProtection.SystemWeb.DataProtectionStartup.
-->
<add key="aspnet:dataProtectionStartupType" value="" />
</appSettings>
<system.web>
<machineKey compatibilityMode="Framework45" dataProtectorType="Microsoft.AspNet.DataProtection.SystemWeb.CompatibilityDataProtector, Microsoft.AspNet.DataProtection.SystemWeb" />
</system.web>
</configuration>

View File

@ -0,0 +1,86 @@
// 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 Microsoft.AspNet.Cryptography;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection
{
/// <summary>
/// Extension methods for working with <see cref="IActivator"/>.
/// </summary>
internal static class ActivatorExtensions
{
/// <summary>
/// Creates an instance of <paramref name="implementationTypeName"/> and ensures
/// that it is assignable to <typeparamref name="T"/>.
/// </summary>
public static T CreateInstance<T>(this IActivator activator, [NotNull] string implementationTypeName)
where T : class
{
return activator.CreateInstance(typeof(T), implementationTypeName) as T
?? CryptoUtil.Fail<T>("CreateInstance returned null.");
}
/// <summary>
/// Returns a <see cref="IActivator"/> given an <see cref="IServiceProvider"/>.
/// Guaranteed to return non-null, even if <paramref name="serviceProvider"/> is null.
/// </summary>
public static IActivator GetActivator(this IServiceProvider serviceProvider)
{
return (serviceProvider != null)
? (serviceProvider.GetService<IActivator>() ?? new SimpleActivator(serviceProvider))
: SimpleActivator.DefaultWithoutServices;
}
/// <summary>
/// A simplified default implementation of <see cref="IActivator"/> that understands
/// how to call ctors which take <see cref="IServiceProvider"/>.
/// </summary>
private sealed class SimpleActivator : IActivator
{
/// <summary>
/// A default <see cref="SimpleActivator"/> whose wrapped <see cref="IServiceProvider"/> is null.
/// </summary>
internal static readonly SimpleActivator DefaultWithoutServices = new SimpleActivator(null);
private readonly IServiceProvider _services;
public SimpleActivator(IServiceProvider services)
{
_services = services;
}
public object CreateInstance(Type expectedBaseType, string implementationTypeName)
{
// Would the assignment even work?
var implementationType = Type.GetType(implementationTypeName, throwOnError: true);
expectedBaseType.AssertIsAssignableFrom(implementationType);
// If no IServiceProvider was specified, prefer .ctor() [if it exists]
if (_services == null)
{
var ctorParameterless = implementationType.GetConstructor(Type.EmptyTypes);
if (ctorParameterless != null)
{
return Activator.CreateInstance(implementationType);
}
}
// If an IServiceProvider was specified or if .ctor() doesn't exist, prefer .ctor(IServiceProvider) [if it exists]
var ctorWhichTakesServiceProvider = implementationType.GetConstructor(new Type[] { typeof(IServiceProvider) });
if (ctorWhichTakesServiceProvider != null)
{
return ctorWhichTakesServiceProvider.Invoke(new[] { _services });
}
// Finally, prefer .ctor() as an ultimate fallback.
// This will throw if the ctor cannot be called.
return Activator.CreateInstance(implementationType);
}
}
}
}

View File

@ -0,0 +1,13 @@
// 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.DataProtection
{
/// <summary>
/// Signifies that the <see cref="RegistryPolicyResolver"/> should bind this property from the registry.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class ApplyPolicyAttribute : Attribute { }
}

View File

@ -0,0 +1,55 @@
// 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.Cryptography;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
internal static class AlgorithmAssert
{
// Our analysis re: IV collision resistance for CBC only holds if we're working with block ciphers
// with a block length of 64 bits or greater.
private const uint SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BITS = 64;
// Min security bar: encryption algorithm must have a min 128-bit key.
private const uint SYMMETRIC_ALG_MIN_KEY_LENGTH_IN_BITS = 128;
// Min security bar: authentication tag must have at least 128 bits of output.
private const uint HASH_ALG_MIN_DIGEST_LENGTH_IN_BITS = 128;
// Since we're performing some stack allocs based on these buffers, make sure we don't explode.
private const uint MAX_SIZE_IN_BITS = Constants.MAX_STACKALLOC_BYTES * 8;
public static void IsAllowableSymmetricAlgorithmBlockSize(uint blockSizeInBits)
{
if (!IsValidCore(blockSizeInBits, SYMMETRIC_ALG_MIN_BLOCK_SIZE_IN_BITS))
{
throw new InvalidOperationException(Resources.FormatAlgorithmAssert_BadBlockSize(blockSizeInBits));
}
}
public static void IsAllowableSymmetricAlgorithmKeySize(uint keySizeInBits)
{
if (!IsValidCore(keySizeInBits, SYMMETRIC_ALG_MIN_KEY_LENGTH_IN_BITS))
{
throw new InvalidOperationException(Resources.FormatAlgorithmAssert_BadKeySize(keySizeInBits));
}
}
public static void IsAllowableValidationAlgorithmDigestSize(uint digestSizeInBits)
{
if (!IsValidCore(digestSizeInBits, HASH_ALG_MIN_DIGEST_LENGTH_IN_BITS))
{
throw new InvalidOperationException(Resources.FormatAlgorithmAssert_BadDigestSize(digestSizeInBits));
}
}
private static bool IsValidCore(uint value, uint minValue)
{
return (value % 8 == 0) // must be whole bytes
&& (value >= minValue) // must meet our basic security requirements
&& (value <= MAX_SIZE_IN_BITS); // mustn't overflow our stack
}
}
}

View File

@ -0,0 +1,200 @@
// 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.Cryptography;
using Microsoft.AspNet.Cryptography.Cng;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Options for configuring authenticated encryption algorithms.
/// </summary>
public sealed class AuthenticatedEncryptionOptions : IInternalAuthenticatedEncryptionOptions
{
/// <summary>
/// The algorithm to use for symmetric encryption (confidentiality).
/// </summary>
/// <remarks>
/// The default value is <see cref="EncryptionAlgorithm.AES_256_CBC"/>.
/// </remarks>
public EncryptionAlgorithm EncryptionAlgorithm { get; set; } = EncryptionAlgorithm.AES_256_CBC;
/// <summary>
/// The algorithm to use for message authentication (tamper-proofing).
/// </summary>
/// <remarks>
/// The default value is <see cref="ValidationAlgorithm.HMACSHA256"/>.
/// This property is ignored if <see cref="EncryptionAlgorithm"/> specifies a 'GCM' algorithm.
/// </remarks>
public ValidationAlgorithm ValidationAlgorithm { get; set; } = ValidationAlgorithm.HMACSHA256;
/// <summary>
/// Validates that this <see cref="AuthenticatedEncryptionOptions"/> is well-formed, i.e.,
/// that the specified algorithms actually exist and that they can be instantiated properly.
/// An exception will be thrown if validation fails.
/// </summary>
public void Validate()
{
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8));
try
{
encryptor.PerformSelfTest();
}
finally
{
(encryptor as IDisposable)?.Dispose();
}
}
/*
* HELPER ROUTINES
*/
internal IAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(ISecret secret)
{
return CreateImplementationOptions()
.ToConfiguration()
.CreateDescriptorFromSecret(secret)
.CreateEncryptorInstance();
}
internal IInternalAuthenticatedEncryptionOptions CreateImplementationOptions()
{
if (IsGcmAlgorithm(EncryptionAlgorithm))
{
// GCM requires CNG, and CNG is only supported on Windows.
if (!OSVersionUtil.IsWindows())
{
throw new PlatformNotSupportedException(Resources.Platform_WindowsRequiredForGcm);
}
return new CngGcmAuthenticatedEncryptionOptions()
{
EncryptionAlgorithm = GetBCryptAlgorithmName(EncryptionAlgorithm),
EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(EncryptionAlgorithm)
};
}
else
{
if (OSVersionUtil.IsWindows())
{
// CNG preferred over managed implementations if running on Windows
return new CngCbcAuthenticatedEncryptionOptions()
{
EncryptionAlgorithm = GetBCryptAlgorithmName(EncryptionAlgorithm),
EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(EncryptionAlgorithm),
HashAlgorithm = GetBCryptAlgorithmName(ValidationAlgorithm)
};
}
else
{
// Use managed implementations as a fallback
return new ManagedAuthenticatedEncryptionOptions()
{
EncryptionAlgorithmType = GetManagedTypeForAlgorithm(EncryptionAlgorithm),
EncryptionAlgorithmKeySize = GetAlgorithmKeySizeInBits(EncryptionAlgorithm),
ValidationAlgorithmType = GetManagedTypeForAlgorithm(ValidationAlgorithm)
};
}
}
}
private static int GetAlgorithmKeySizeInBits(EncryptionAlgorithm algorithm)
{
switch (algorithm)
{
case EncryptionAlgorithm.AES_128_CBC:
case EncryptionAlgorithm.AES_128_GCM:
return 128;
case EncryptionAlgorithm.AES_192_CBC:
case EncryptionAlgorithm.AES_192_GCM:
return 192;
case EncryptionAlgorithm.AES_256_CBC:
case EncryptionAlgorithm.AES_256_GCM:
return 256;
default:
throw new ArgumentOutOfRangeException(nameof(algorithm));
}
}
private static string GetBCryptAlgorithmName(EncryptionAlgorithm algorithm)
{
switch (algorithm)
{
case EncryptionAlgorithm.AES_128_CBC:
case EncryptionAlgorithm.AES_192_CBC:
case EncryptionAlgorithm.AES_256_CBC:
case EncryptionAlgorithm.AES_128_GCM:
case EncryptionAlgorithm.AES_192_GCM:
case EncryptionAlgorithm.AES_256_GCM:
return Constants.BCRYPT_AES_ALGORITHM;
default:
throw new ArgumentOutOfRangeException(nameof(algorithm));
}
}
private static string GetBCryptAlgorithmName(ValidationAlgorithm algorithm)
{
switch (algorithm)
{
case ValidationAlgorithm.HMACSHA256:
return Constants.BCRYPT_SHA256_ALGORITHM;
case ValidationAlgorithm.HMACSHA512:
return Constants.BCRYPT_SHA512_ALGORITHM;
default:
throw new ArgumentOutOfRangeException(nameof(algorithm));
}
}
private static Type GetManagedTypeForAlgorithm(EncryptionAlgorithm algorithm)
{
switch (algorithm)
{
case EncryptionAlgorithm.AES_128_CBC:
case EncryptionAlgorithm.AES_192_CBC:
case EncryptionAlgorithm.AES_256_CBC:
case EncryptionAlgorithm.AES_128_GCM:
case EncryptionAlgorithm.AES_192_GCM:
case EncryptionAlgorithm.AES_256_GCM:
return typeof(Aes);
default:
throw new ArgumentOutOfRangeException(nameof(algorithm));
}
}
private static Type GetManagedTypeForAlgorithm(ValidationAlgorithm algorithm)
{
switch (algorithm)
{
case ValidationAlgorithm.HMACSHA256:
return typeof(HMACSHA256);
case ValidationAlgorithm.HMACSHA512:
return typeof(HMACSHA512);
default:
throw new ArgumentOutOfRangeException(nameof(algorithm));
}
}
internal static bool IsGcmAlgorithm(EncryptionAlgorithm algorithm)
{
return (EncryptionAlgorithm.AES_128_GCM <= algorithm && algorithm <= EncryptionAlgorithm.AES_256_GCM);
}
IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionOptions.ToConfiguration()
{
return new AuthenticatedEncryptorConfiguration(this);
}
}
}

View File

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.DataProtection.Cng;
using Microsoft.AspNet.Cryptography;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
@ -31,5 +31,25 @@ namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
return retVal;
}
}
/// <summary>
/// Performs a self-test of this encryptor by running a sample payload through an
/// encrypt-then-decrypt operation. Throws if the operation fails.
/// </summary>
public static void PerformSelfTest(this IAuthenticatedEncryptor encryptor)
{
// Arrange
Guid plaintextAsGuid = Guid.NewGuid();
byte[] plaintextAsBytes = plaintextAsGuid.ToByteArray();
byte[] aad = Guid.NewGuid().ToByteArray();
// Act
byte[] protectedData = encryptor.Encrypt(new ArraySegment<byte>(plaintextAsBytes), new ArraySegment<byte>(aad));
byte[] roundTrippedData = encryptor.Decrypt(new ArraySegment<byte>(protectedData), new ArraySegment<byte>(aad));
// Assert
CryptoUtil.Assert(roundTrippedData != null && roundTrippedData.Length == plaintextAsBytes.Length && plaintextAsGuid == new Guid(roundTrippedData),
"Plaintext did not round-trip properly through the authenticated encryptor.");
}
}
}

View File

@ -5,15 +5,16 @@ using System;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.Cryptography.Cng;
using Microsoft.AspNet.Cryptography.SafeHandles;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNet.DataProtection.Cng;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Options for configuring an authenticated encryption mechanism which uses
/// Windows CNG algorithms in CBC encryption + HMAC validation modes.
/// Windows CNG algorithms in CBC encryption + HMAC authentication modes.
/// </summary>
public sealed class CngCbcAuthenticatedEncryptorConfigurationOptions : IInternalConfigurationOptions
public sealed class CngCbcAuthenticatedEncryptionOptions : IInternalAuthenticatedEncryptionOptions
{
/// <summary>
/// The name of the algorithm to use for symmetric encryption.
@ -21,9 +22,11 @@ namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
/// 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 algorithm must support CBC-style encryption and must have a block size of 64 bits
/// or greater.
/// The default value is 'AES'.
/// </remarks>
[ApplyPolicy]
public string EncryptionAlgorithm { get; set; } = Constants.BCRYPT_AES_ALGORITHM;
/// <summary>
@ -34,6 +37,7 @@ namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
/// <remarks>
/// The default value is null.
/// </remarks>
[ApplyPolicy]
public string EncryptionAlgorithmProvider { get; set; } = null;
/// <summary>
@ -44,6 +48,7 @@ namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
/// The key length must be 128 bits or greater.
/// The default value is 256.
/// </remarks>
[ApplyPolicy]
public int EncryptionAlgorithmKeySize { get; set; } = 256;
/// <summary>
@ -56,6 +61,7 @@ namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
/// of 128 bits or greater.
/// The default value is 'SHA256'.
/// </remarks>
[ApplyPolicy]
public string HashAlgorithm { get; set; } = Constants.BCRYPT_SHA256_ALGORITHM;
/// <summary>
@ -66,124 +72,109 @@ namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
/// <remarks>
/// The default value is null.
/// </remarks>
[ApplyPolicy]
public string HashAlgorithmProvider { get; set; } = null;
/// <summary>
/// Makes a duplicate of this object, which allows the original object to remain mutable.
/// Validates that this <see cref="CngCbcAuthenticatedEncryptionOptions"/> is well-formed, i.e.,
/// that the specified algorithms actually exist and that they can be instantiated properly.
/// An exception will be thrown if validation fails.
/// </summary>
internal CngCbcAuthenticatedEncryptorConfigurationOptions Clone()
public void Validate()
{
return new CngCbcAuthenticatedEncryptorConfigurationOptions()
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
using (var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8)))
{
EncryptionAlgorithm = this.EncryptionAlgorithm,
EncryptionAlgorithmKeySize = this.EncryptionAlgorithmKeySize,
EncryptionAlgorithmProvider = this.EncryptionAlgorithmProvider,
HashAlgorithm = this.HashAlgorithm,
HashAlgorithmProvider = this.HashAlgorithmProvider
};
encryptor.PerformSelfTest();
}
}
internal IAuthenticatedEncryptor CreateAuthenticatedEncryptor([NotNull] ISecret secret)
/*
* HELPER ROUTINES
*/
internal CbcAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(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 Secret(secret),
symmetricAlgorithmHandle: encryptionAlgorithmHandle,
symmetricAlgorithmKeySizeInBytes: encryptionAlgorithmKeySizeInBits / 8,
hmacAlgorithmHandle: hashAlgorithmHandle);
symmetricAlgorithmHandle: GetSymmetricBlockCipherAlgorithmHandle(),
symmetricAlgorithmKeySizeInBytes: (uint)(EncryptionAlgorithmKeySize / 8),
hmacAlgorithmHandle: GetHmacAlgorithmHandle());
}
private static BCryptAlgorithmHandle GetEncryptionAlgorithmHandleAndCheckKeySize(string encryptionAlgorithm, string encryptionAlgorithmProvider, uint keyLengthInBits)
private BCryptAlgorithmHandle GetHmacAlgorithmHandle()
{
// basic argument checking
if (String.IsNullOrEmpty(HashAlgorithm))
{
throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(HashAlgorithm));
}
BCryptAlgorithmHandle algorithmHandle = null;
// Special-case cached providers
if (encryptionAlgorithmProvider == null)
if (HashAlgorithmProvider == null)
{
if (encryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_CBC; }
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(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);
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");
AlgorithmAssert.IsAllowableValidationAlgorithmDigestSize(checked(digestSize * 8));
// all good!
return algorithmHandle;
}
private static uint GetKeySizeInBits(int value)
private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle()
{
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))
// basic argument checking
if (String.IsNullOrEmpty(EncryptionAlgorithm))
{
throw Error.Common_PropertyCannotBeNullOrEmpty(propertyName);
throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm));
}
return value;
if (EncryptionAlgorithmKeySize < 0)
{
throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize));
}
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 key size & block size
AlgorithmAssert.IsAllowableSymmetricAlgorithmBlockSize(checked(algorithmHandle.GetCipherBlockLength() * 8));
AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)EncryptionAlgorithmKeySize));
// make sure the provided key length is valid
algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)EncryptionAlgorithmKeySize);
// all good!
return algorithmHandle;
}
IAuthenticatedEncryptor IInternalConfigurationOptions.CreateAuthenticatedEncryptor(ISecret secret)
IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionOptions.ToConfiguration()
{
return CreateAuthenticatedEncryptor(secret);
return new CngCbcAuthenticatedEncryptorConfiguration(this);
}
}
}

View File

@ -1,76 +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.Xml.Linq;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.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

@ -1,30 +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.Framework.OptionsModel;
namespace Microsoft.AspNet.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] IOptions<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 = Secret.Random(KDK_SIZE_IN_BYTES);
return new CngCbcAuthenticatedEncryptorConfiguration(_options, secret);
}
}
}

View File

@ -1,68 +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.Linq;
using System.Xml.Linq;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.DataProtection.XmlEncryption;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
internal sealed class CngCbcAuthenticatedEncryptorConfigurationXmlReader : IAuthenticatedEncryptorConfigurationXmlReader
{
private readonly IServiceProvider _serviceProvider;
public CngCbcAuthenticatedEncryptorConfigurationXmlReader(
[NotNull] IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
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)ActivatorUtilities.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 secret = new Secret(decryptedSecretBytes);
return new CngCbcAuthenticatedEncryptorConfiguration(options, secret);
}
finally
{
Array.Clear(decryptedSecretBytes, 0, decryptedSecretBytes.Length);
}
}
}
}

View File

@ -0,0 +1,123 @@
// 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.Cryptography;
using Microsoft.AspNet.Cryptography.Cng;
using Microsoft.AspNet.Cryptography.SafeHandles;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNet.DataProtection.Cng;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Options for configuring an authenticated encryption mechanism which uses
/// Windows CNG algorithms in GCM encryption + authentication modes.
/// </summary>
public sealed class CngGcmAuthenticatedEncryptionOptions : IInternalAuthenticatedEncryptionOptions
{
/// <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 exactly
/// 128 bits.
/// The default value is 'AES'.
/// </remarks>
[ApplyPolicy]
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>
[ApplyPolicy]
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>
[ApplyPolicy]
public int EncryptionAlgorithmKeySize { get; set; } = 256;
/// <summary>
/// Validates that this <see cref="CngGcmAuthenticatedEncryptionOptions"/> is well-formed, i.e.,
/// that the specified algorithm actually exists and can be instantiated properly.
/// An exception will be thrown if validation fails.
/// </summary>
public void Validate()
{
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
using (var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8)))
{
encryptor.PerformSelfTest();
}
}
/*
* HELPER ROUTINES
*/
internal GcmAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(ISecret secret)
{
return new GcmAuthenticatedEncryptor(
keyDerivationKey: new Secret(secret),
symmetricAlgorithmHandle: GetSymmetricBlockCipherAlgorithmHandle(),
symmetricAlgorithmKeySizeInBytes: (uint)(EncryptionAlgorithmKeySize / 8));
}
private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle()
{
// basic argument checking
if (String.IsNullOrEmpty(EncryptionAlgorithm))
{
throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm));
}
if (EncryptionAlgorithmKeySize < 0)
{
throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize));
}
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 key size & block size
CryptoUtil.Assert(algorithmHandle.GetCipherBlockLength() == 128 / 8, "GCM requires a block cipher algorithm with a 128-bit block size.");
AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)EncryptionAlgorithmKeySize));
// make sure the provided key length is valid
algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)EncryptionAlgorithmKeySize);
// all good!
return algorithmHandle;
}
IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionOptions.ToConfiguration()
{
return new CngGcmAuthenticatedEncryptorConfiguration(this);
}
}
}

View File

@ -1,71 +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.Xml.Linq;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.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

@ -1,30 +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.Framework.OptionsModel;
namespace Microsoft.AspNet.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] IOptions<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 = Secret.Random(KDK_SIZE_IN_BYTES);
return new CngGcmAuthenticatedEncryptorConfiguration(_options, secret);
}
}
}

View File

@ -1,131 +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.AspNet.Cryptography;
using Microsoft.AspNet.Cryptography.Cng;
using Microsoft.AspNet.Cryptography.SafeHandles;
using Microsoft.AspNet.DataProtection.Cng;
namespace Microsoft.AspNet.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 : IInternalConfigurationOptions
{
/// <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 Secret(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;
}
IAuthenticatedEncryptor IInternalConfigurationOptions.CreateAuthenticatedEncryptor(ISecret secret)
{
return CreateAuthenticatedEncryptor(secret);
}
}
}

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.Linq;
using System.Xml.Linq;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.DataProtection.XmlEncryption;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
internal sealed class CngGcmAuthenticatedEncryptorConfigurationXmlReader : IAuthenticatedEncryptorConfigurationXmlReader
{
private readonly IServiceProvider _serviceProvider;
public CngGcmAuthenticatedEncryptorConfigurationXmlReader(
[NotNull] IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
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)ActivatorUtilities.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 secret = new Secret(decryptedSecretBytes);
return new CngGcmAuthenticatedEncryptorConfiguration(options, secret);
}
finally
{
Array.Clear(decryptedSecretBytes, 0, decryptedSecretBytes.Length);
}
}
}
}

View File

@ -0,0 +1,34 @@
// 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.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// Represents a generalized authenticated encryption mechanism.
/// </summary>
public unsafe sealed class AuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration, IInternalAuthenticatedEncryptorConfiguration
{
public AuthenticatedEncryptorConfiguration([NotNull] AuthenticatedEncryptionOptions options)
{
Options = options;
}
public AuthenticatedEncryptionOptions Options { get; }
public IAuthenticatedEncryptorDescriptor CreateNewDescriptor()
{
// generate a 512-bit secret randomly
const int KDK_SIZE_IN_BYTES = 512 / 8;
var secret = Secret.Random(KDK_SIZE_IN_BYTES);
return ((IInternalAuthenticatedEncryptorConfiguration)this).CreateDescriptorFromSecret(secret);
}
IAuthenticatedEncryptorDescriptor IInternalAuthenticatedEncryptorConfiguration.CreateDescriptorFromSecret(ISecret secret)
{
return new AuthenticatedEncryptorDescriptor(Options, secret);
}
}
}

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.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A descriptor which can create an authenticated encryption system based upon the
/// configuration provided by an <see cref="AuthenticatedEncryptionOptions"/> object.
/// </summary>
public sealed class AuthenticatedEncryptorDescriptor : IAuthenticatedEncryptorDescriptor
{
private readonly ISecret _masterKey;
private readonly AuthenticatedEncryptionOptions _options;
public AuthenticatedEncryptorDescriptor([NotNull] AuthenticatedEncryptionOptions options, [NotNull] ISecret masterKey)
{
_options = options;
_masterKey = masterKey;
}
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return _options.CreateAuthenticatedEncryptorInstance(_masterKey);
}
public XmlSerializedDescriptorInfo ExportToXml()
{
// <descriptor>
// <encryption algorithm="..." />
// <validation algorithm="..." /> <!-- only if not GCM -->
// <masterKey requiresEncryption="true">...</masterKey>
// </descriptor>
var encryptionElement = new XElement("encryption",
new XAttribute("algorithm", _options.EncryptionAlgorithm));
var validationElement = (AuthenticatedEncryptionOptions.IsGcmAlgorithm(_options.EncryptionAlgorithm))
? (object)new XComment(" AES-GCM includes a 128-bit authentication tag, no extra validation algorithm required. ")
: (object)new XElement("validation",
new XAttribute("algorithm", _options.ValidationAlgorithm));
var outerElement = new XElement("descriptor",
encryptionElement,
validationElement,
_masterKey.ToMasterKeyElement());
return new XmlSerializedDescriptorInfo(outerElement, typeof(AuthenticatedEncryptorDescriptorDeserializer));
}
}
}

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 System.Linq;
using System.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A class that can deserialize an <see cref="XElement"/> that represents the serialized version
/// of an <see cref="AuthenticatedEncryptorDescriptor"/>.
/// </summary>
public sealed class AuthenticatedEncryptorDescriptorDeserializer : IAuthenticatedEncryptorDescriptorDeserializer
{
/// <summary>
/// Imports the <see cref="AuthenticatedEncryptorDescriptor"/> from serialized XML.
/// </summary>
public IAuthenticatedEncryptorDescriptor ImportFromXml([NotNull] XElement element)
{
// <descriptor>
// <encryption algorithm="..." />
// <validation algorithm="..." /> <!-- only if not GCM -->
// <masterKey requiresEncryption="true">...</masterKey>
// </descriptor>
var options = new AuthenticatedEncryptionOptions();
var encryptionElement = element.Element("encryption");
options.EncryptionAlgorithm = (EncryptionAlgorithm)Enum.Parse(typeof(EncryptionAlgorithm), (string)encryptionElement.Attribute("algorithm"));
// only read <validation> if not GCM
if (!AuthenticatedEncryptionOptions.IsGcmAlgorithm(options.EncryptionAlgorithm))
{
var validationElement = element.Element("validation");
options.ValidationAlgorithm = (ValidationAlgorithm)Enum.Parse(typeof(ValidationAlgorithm), (string)validationElement.Attribute("algorithm"));
}
Secret masterKey = ((string)element.Elements("masterKey").Single()).ToSecret();
return new AuthenticatedEncryptorDescriptor(options, masterKey);
}
}
}

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 Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// Represents a configured authenticated encryption mechanism which uses
/// Windows CNG algorithms in CBC encryption + HMAC authentication modes.
/// </summary>
public unsafe sealed class CngCbcAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration, IInternalAuthenticatedEncryptorConfiguration
{
public CngCbcAuthenticatedEncryptorConfiguration([NotNull] CngCbcAuthenticatedEncryptionOptions options)
{
Options = options;
}
public CngCbcAuthenticatedEncryptionOptions Options { get; }
public IAuthenticatedEncryptorDescriptor CreateNewDescriptor()
{
// generate a 512-bit secret randomly
const int KDK_SIZE_IN_BYTES = 512 / 8;
var secret = Secret.Random(KDK_SIZE_IN_BYTES);
return ((IInternalAuthenticatedEncryptorConfiguration)this).CreateDescriptorFromSecret(secret);
}
IAuthenticatedEncryptorDescriptor IInternalAuthenticatedEncryptorConfiguration.CreateDescriptorFromSecret(ISecret secret)
{
return new CngCbcAuthenticatedEncryptorDescriptor(Options, secret);
}
}
}

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.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A descriptor which can create an authenticated encryption system based upon the
/// configuration provided by an <see cref="CngCbcAuthenticatedEncryptionOptions"/> object.
/// </summary>
public sealed class CngCbcAuthenticatedEncryptorDescriptor : IAuthenticatedEncryptorDescriptor
{
public CngCbcAuthenticatedEncryptorDescriptor([NotNull] CngCbcAuthenticatedEncryptionOptions options, [NotNull] ISecret masterKey)
{
Options = options;
MasterKey = masterKey;
}
internal ISecret MasterKey { get; }
internal CngCbcAuthenticatedEncryptionOptions Options { get; }
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return Options.CreateAuthenticatedEncryptorInstance(MasterKey);
}
public XmlSerializedDescriptorInfo ExportToXml()
{
// <descriptor>
// <!-- Windows CNG-CBC -->
// <encryption algorithm="..." keyLength="..." [provider="..."] />
// <hash algorithm="..." [provider="..."] />
// <masterKey>...</masterKey>
// </descriptor>
var encryptionElement = new XElement("encryption",
new XAttribute("algorithm", Options.EncryptionAlgorithm),
new XAttribute("keyLength", Options.EncryptionAlgorithmKeySize));
if (Options.EncryptionAlgorithmProvider != null)
{
encryptionElement.SetAttributeValue("provider", Options.EncryptionAlgorithmProvider);
}
var hashElement = new XElement("hash",
new XAttribute("algorithm", Options.HashAlgorithm));
if (Options.HashAlgorithmProvider != null)
{
hashElement.SetAttributeValue("provider", Options.HashAlgorithmProvider);
}
var rootElement = new XElement("descriptor",
new XComment(" Algorithms provided by Windows CNG, using CBC-mode encryption with HMAC validation "),
encryptionElement,
hashElement,
MasterKey.ToMasterKeyElement());
return new XmlSerializedDescriptorInfo(rootElement, typeof(CngCbcAuthenticatedEncryptorDescriptorDeserializer));
}
}
}

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 System.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A class that can deserialize an <see cref="XElement"/> that represents the serialized version
/// of an <see cref="CngCbcAuthenticatedEncryptorDescriptor"/>.
/// </summary>
public sealed class CngCbcAuthenticatedEncryptorDescriptorDeserializer : IAuthenticatedEncryptorDescriptorDeserializer
{
/// <summary>
/// Imports the <see cref="CngCbcAuthenticatedEncryptorDescriptor"/> from serialized XML.
/// </summary>
public IAuthenticatedEncryptorDescriptor ImportFromXml([NotNull] XElement element)
{
// <descriptor>
// <!-- Windows CNG-CBC -->
// <encryption algorithm="..." keyLength="..." [provider="..."] />
// <hash algorithm="..." [provider="..."] />
// <masterKey>...</masterKey>
// </descriptor>
var options = new CngCbcAuthenticatedEncryptionOptions();
var encryptionElement = element.Element("encryption");
options.EncryptionAlgorithm = (string)encryptionElement.Attribute("algorithm");
options.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength");
options.EncryptionAlgorithmProvider = (string)encryptionElement.Attribute("provider"); // could be null
var hashElement = element.Element("hash");
options.HashAlgorithm = (string)hashElement.Attribute("algorithm");
options.HashAlgorithmProvider = (string)hashElement.Attribute("provider"); // could be null
Secret masterKey = ((string)element.Element("masterKey")).ToSecret();
return new CngCbcAuthenticatedEncryptorDescriptor(options, masterKey);
}
}
}

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 Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// Represents a configured authenticated encryption mechanism which uses
/// Windows CNG algorithms in GCM encryption + authentication modes.
/// </summary>
public unsafe sealed class CngGcmAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration, IInternalAuthenticatedEncryptorConfiguration
{
public CngGcmAuthenticatedEncryptorConfiguration([NotNull] CngGcmAuthenticatedEncryptionOptions options)
{
Options = options;
}
public CngGcmAuthenticatedEncryptionOptions Options { get; }
public IAuthenticatedEncryptorDescriptor CreateNewDescriptor()
{
// generate a 512-bit secret randomly
const int KDK_SIZE_IN_BYTES = 512 / 8;
var secret = Secret.Random(KDK_SIZE_IN_BYTES);
return ((IInternalAuthenticatedEncryptorConfiguration)this).CreateDescriptorFromSecret(secret);
}
IAuthenticatedEncryptorDescriptor IInternalAuthenticatedEncryptorConfiguration.CreateDescriptorFromSecret(ISecret secret)
{
return new CngGcmAuthenticatedEncryptorDescriptor(Options, secret);
}
}
}

View File

@ -0,0 +1,55 @@
// 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.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A descriptor which can create an authenticated encryption system based upon the
/// configuration provided by an <see cref="CngGcmAuthenticatedEncryptionOptions"/> object.
/// </summary>
public sealed class CngGcmAuthenticatedEncryptorDescriptor : IAuthenticatedEncryptorDescriptor
{
public CngGcmAuthenticatedEncryptorDescriptor([NotNull] CngGcmAuthenticatedEncryptionOptions options, [NotNull] ISecret masterKey)
{
Options = options;
MasterKey = masterKey;
}
internal ISecret MasterKey { get; }
internal CngGcmAuthenticatedEncryptionOptions Options { get; }
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return Options.CreateAuthenticatedEncryptorInstance(MasterKey);
}
public XmlSerializedDescriptorInfo ExportToXml()
{
// <descriptor>
// <!-- Windows CNG-GCM -->
// <encryption algorithm="..." keyLength="..." [provider="..."] />
// <masterKey>...</masterKey>
// </descriptor>
var encryptionElement = new XElement("encryption",
new XAttribute("algorithm", Options.EncryptionAlgorithm),
new XAttribute("keyLength", Options.EncryptionAlgorithmKeySize));
if (Options.EncryptionAlgorithmProvider != null)
{
encryptionElement.SetAttributeValue("provider", Options.EncryptionAlgorithmProvider);
}
var rootElement = new XElement("descriptor",
new XComment(" Algorithms provided by Windows CNG, using GCM mode encryption and validation "),
encryptionElement,
MasterKey.ToMasterKeyElement());
return new XmlSerializedDescriptorInfo(rootElement, typeof(CngGcmAuthenticatedEncryptorDescriptorDeserializer));
}
}
}

View File

@ -0,0 +1,39 @@
// 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.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A class that can deserialize an <see cref="XElement"/> that represents the serialized version
/// of an <see cref="CngGcmAuthenticatedEncryptorDescriptor"/>.
/// </summary>
public sealed class CngGcmAuthenticatedEncryptorDescriptorDeserializer : IAuthenticatedEncryptorDescriptorDeserializer
{
/// <summary>
/// Imports the <see cref="CngCbcAuthenticatedEncryptorDescriptor"/> from serialized XML.
/// </summary>
public IAuthenticatedEncryptorDescriptor ImportFromXml([NotNull] XElement element)
{
// <descriptor>
// <!-- Windows CNG-GCM -->
// <encryption algorithm="..." keyLength="..." [provider="..."] />
// <masterKey>...</masterKey>
// </descriptor>
var options = new CngGcmAuthenticatedEncryptionOptions();
var encryptionElement = element.Element("encryption");
options.EncryptionAlgorithm = (string)encryptionElement.Attribute("algorithm");
options.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength");
options.EncryptionAlgorithmProvider = (string)encryptionElement.Attribute("provider"); // could be null
Secret masterKey = ((string)element.Element("masterKey")).ToSecret();
return new CngGcmAuthenticatedEncryptorDescriptor(options, masterKey);
}
}
}

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.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// The basic configuration that serves as a factory for types related to authenticated encryption.
/// </summary>
public interface IAuthenticatedEncryptorConfiguration
{
/// <summary>
/// Creates a new <see cref="IAuthenticatedEncryptorDescriptor"/> instance based on this
/// configuration. The newly-created instance contains unique key material and is distinct
/// from all other descriptors created by the <see cref="CreateNewDescriptor"/> method.
/// </summary>
/// <returns>A unique <see cref="IAuthenticatedEncryptorDescriptor"/>.</returns>
IAuthenticatedEncryptorDescriptor CreateNewDescriptor();
}
}

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.Xml.Linq;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A self-contained descriptor that wraps all information (including secret key
/// material) necessary to create an instance of an <see cref="IAuthenticatedEncryptor"/>.
/// </summary>
public interface IAuthenticatedEncryptorDescriptor
{
/// <summary>
/// Creates an <see cref="IAuthenticatedEncryptor"/> instance based on the current descriptor.
/// </summary>
/// <returns>An <see cref="IAuthenticatedEncryptor"/> instance.</returns>
/// <remarks>
/// For a given descriptor, any two instances returned by this method should
/// be considered equivalent, e.g., the payload returned by one's <see cref="IAuthenticatedEncryptor.Encrypt(ArraySegment{byte}, ArraySegment{byte})"/>
/// method should be consumable by the other's <see cref="IAuthenticatedEncryptor.Decrypt(ArraySegment{byte}, ArraySegment{byte})"/> method.
/// </remarks>
IAuthenticatedEncryptor CreateEncryptorInstance();
/// <summary>
/// Exports the current descriptor to XML.
/// </summary>
/// <returns>
/// An <see cref="XmlSerializedDescriptorInfo"/> wrapping the <see cref="XElement"/> which represents the serialized
/// current descriptor object. The deserializer type must be assignable to <see cref="IAuthenticatedEncryptorDescriptorDeserializer"/>.
/// </returns>
/// <remarks>
/// If an element contains sensitive information (such as key material), the
/// element should be marked via the <see cref="XmlExtensions.MarkAsRequiresEncryption(XElement)" />
/// extension method, and the caller should encrypt the element before persisting
/// the XML to storage.
/// </remarks>
XmlSerializedDescriptorInfo ExportToXml();
}
}

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;
using System.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// The basic interface for deserializing an XML element into an <see cref="IAuthenticatedEncryptorDescriptor"/>.
/// </summary>
public interface IAuthenticatedEncryptorDescriptorDeserializer
{
/// <summary>
/// Deserializes the specified XML element.
/// </summary>
/// <param name="element">The element to deserialize.</param>
/// <returns>The <see cref="IAuthenticatedEncryptorDescriptor"/> represented by <paramref name="element"/>.</returns>
IAuthenticatedEncryptorDescriptor ImportFromXml([NotNull] XElement element);
}
}

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.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
// This type is not public because we don't want to lock ourselves into a contract stating
// that a descriptor is simply a configuration plus a single serializable, reproducible secret.
/// <summary>
/// A type that knows how to create instances of an <see cref="IAuthenticatedEncryptorDescriptor"/>
/// given specific secret key material.
/// </summary>
internal interface IInternalAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration
{
/// <summary>
/// Creates a new <see cref="IAuthenticatedEncryptorDescriptor"/> instance from this
/// configuration given specific secret key material.
/// </summary>
/// <returns></returns>
IAuthenticatedEncryptorDescriptor CreateDescriptorFromSecret(ISecret secret);
}
}

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.Framework.Internal;
using System.Security.Cryptography;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// Represents a configured authenticated encryption mechanism which uses
/// managed <see cref="SymmetricAlgorithm"/> and <see cref="KeyedHashAlgorithm"/> types.
/// </summary>
public sealed class ManagedAuthenticatedEncryptorConfiguration : IAuthenticatedEncryptorConfiguration, IInternalAuthenticatedEncryptorConfiguration
{
public ManagedAuthenticatedEncryptorConfiguration([NotNull] ManagedAuthenticatedEncryptionOptions options)
{
Options = options;
}
public ManagedAuthenticatedEncryptionOptions Options { get; }
public IAuthenticatedEncryptorDescriptor CreateNewDescriptor()
{
// generate a 512-bit secret randomly
const int KDK_SIZE_IN_BYTES = 512 / 8;
var secret = Secret.Random(KDK_SIZE_IN_BYTES);
return ((IInternalAuthenticatedEncryptorConfiguration)this).CreateDescriptorFromSecret(secret);
}
IAuthenticatedEncryptorDescriptor IInternalAuthenticatedEncryptorConfiguration.CreateDescriptorFromSecret(ISecret secret)
{
return new ManagedAuthenticatedEncryptorDescriptor(Options, secret);
}
}
}

View File

@ -0,0 +1,87 @@
// 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 System.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A descriptor which can create an authenticated encryption system based upon the
/// configuration provided by an <see cref="ManagedAuthenticatedEncryptionOptions"/> object.
/// </summary>
public sealed class ManagedAuthenticatedEncryptorDescriptor : IAuthenticatedEncryptorDescriptor
{
public ManagedAuthenticatedEncryptorDescriptor([NotNull] ManagedAuthenticatedEncryptionOptions options, [NotNull] ISecret masterKey)
{
Options = options;
MasterKey = masterKey;
}
internal ISecret MasterKey { get; }
internal ManagedAuthenticatedEncryptionOptions Options { get; }
public IAuthenticatedEncryptor CreateEncryptorInstance()
{
return Options.CreateAuthenticatedEncryptorInstance(MasterKey);
}
public XmlSerializedDescriptorInfo ExportToXml()
{
// <descriptor>
// <!-- managed implementations -->
// <encryption algorithm="..." keyLength="..." />
// <validation algorithm="..." />
// <masterKey>...</masterKey>
// </descriptor>
var encryptionElement = new XElement("encryption",
new XAttribute("algorithm", TypeToFriendlyName(Options.EncryptionAlgorithmType)),
new XAttribute("keyLength", Options.EncryptionAlgorithmKeySize));
var validationElement = new XElement("validation",
new XAttribute("algorithm", TypeToFriendlyName(Options.ValidationAlgorithmType)));
var rootElement = new XElement("descriptor",
new XComment(" Algorithms provided by specified SymmetricAlgorithm and KeyedHashAlgorithm "),
encryptionElement,
validationElement,
MasterKey.ToMasterKeyElement());
return new XmlSerializedDescriptorInfo(rootElement, typeof(ManagedAuthenticatedEncryptorDescriptorDeserializer));
}
// Any changes to this method should also be be reflected
// in ManagedAuthenticatedEncryptorDescriptorDeserializer.FriendlyNameToType.
private static string TypeToFriendlyName(Type type)
{
if (type == typeof(Aes))
{
return nameof(Aes);
}
else if (type == typeof(HMACSHA1))
{
return nameof(HMACSHA1);
}
else if (type == typeof(HMACSHA256))
{
return nameof(HMACSHA256);
}
else if (type == typeof(HMACSHA384))
{
return nameof(HMACSHA384);
}
else if (type == typeof(HMACSHA512))
{
return nameof(HMACSHA512);
}
else
{
return type.AssemblyQualifiedName;
}
}
}
}

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.Security.Cryptography;
using System.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// A class that can deserialize an <see cref="XElement"/> that represents the serialized version
/// of an <see cref="ManagedAuthenticatedEncryptorDescriptor"/>.
/// </summary>
public sealed class ManagedAuthenticatedEncryptorDescriptorDeserializer : IAuthenticatedEncryptorDescriptorDeserializer
{
/// <summary>
/// Imports the <see cref="ManagedAuthenticatedEncryptorDescriptor"/> from serialized XML.
/// </summary>
public IAuthenticatedEncryptorDescriptor ImportFromXml([NotNull] XElement element)
{
// <descriptor>
// <!-- managed implementations -->
// <encryption algorithm="..." keyLength="..." />
// <validation algorithm="..." />
// <masterKey>...</masterKey>
// </descriptor>
var options = new ManagedAuthenticatedEncryptionOptions();
var encryptionElement = element.Element("encryption");
options.EncryptionAlgorithmType = FriendlyNameToType((string)encryptionElement.Attribute("algorithm"));
options.EncryptionAlgorithmKeySize = (int)encryptionElement.Attribute("keyLength");
var validationElement = element.Element("validation");
options.ValidationAlgorithmType = FriendlyNameToType((string)validationElement.Attribute("algorithm"));
Secret masterKey = ((string)element.Element("masterKey")).ToSecret();
return new ManagedAuthenticatedEncryptorDescriptor(options, masterKey);
}
// Any changes to this method should also be be reflected
// in ManagedAuthenticatedEncryptorDescriptor.TypeToFriendlyName.
private static Type FriendlyNameToType(string typeName)
{
if (typeName == nameof(Aes))
{
return typeof(Aes);
}
else if (typeName == nameof(HMACSHA1))
{
return typeof(HMACSHA1);
}
else if (typeName == nameof(HMACSHA256))
{
return typeof(HMACSHA256);
}
else if (typeName == nameof(HMACSHA384))
{
return typeof(HMACSHA384);
}
else if (typeName == nameof(HMACSHA512))
{
return typeof(HMACSHA512);
}
else
{
return Type.GetType(typeName, throwOnError: true);
}
}
}
}

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 System.Xml.Linq;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
internal unsafe static class SecretExtensions
{
/// <summary>
/// Converts an <see cref="ISecret"/> to an &lt;masterKey&gt; element which is marked
/// as requiring encryption.
/// </summary>
/// <returns></returns>
public static XElement ToMasterKeyElement(this ISecret secret)
{
// Technically we'll be keeping the unprotected secret around in memory as
// a string, so it can get moved by the GC, but we should be good citizens
// and try to pin / clear our our temporary buffers regardless.
byte[] unprotectedSecretRawBytes = new byte[secret.Length];
string unprotectedSecretAsBase64String;
fixed (byte* __unused__ = unprotectedSecretRawBytes)
{
try
{
secret.WriteSecretIntoBuffer(new ArraySegment<byte>(unprotectedSecretRawBytes));
unprotectedSecretAsBase64String = Convert.ToBase64String(unprotectedSecretRawBytes);
}
finally
{
Array.Clear(unprotectedSecretRawBytes, 0, unprotectedSecretRawBytes.Length);
}
}
XElement masterKeyElement = new XElement("masterKey",
new XComment(" Warning: the key below is in an unencrypted form. "),
new XElement("value", unprotectedSecretAsBase64String));
masterKeyElement.MarkAsRequiresEncryption();
return masterKeyElement;
}
/// <summary>
/// Converts a base64-encoded string into an <see cref="ISecret"/>.
/// </summary>
/// <returns></returns>
public static Secret ToSecret(this string base64String)
{
byte[] unprotectedSecret = Convert.FromBase64String(base64String);
fixed (byte* __unused__ = unprotectedSecret)
{
try
{
return new Secret(unprotectedSecret);
}
finally
{
Array.Clear(unprotectedSecret, 0, unprotectedSecret.Length);
}
}
}
}
}

View File

@ -0,0 +1,26 @@
// 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.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
public static class XmlExtensions
{
internal static bool IsMarkedAsRequiringEncryption(this XElement element)
{
return ((bool?)element.Attribute(XmlConstants.RequiresEncryptionAttributeName)).GetValueOrDefault();
}
/// <summary>
/// Marks the provided <see cref="XElement"/> as requiring encryption before being persisted
/// to storage. Use when implementing <see cref="IAuthenticatedEncryptorDescriptor.ExportToXml"/>.
/// </summary>
public static void MarkAsRequiresEncryption([NotNull] this XElement element)
{
element.SetAttributeValue(XmlConstants.RequiresEncryptionAttributeName, true);
}
}
}

View File

@ -0,0 +1,48 @@
// 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.Xml.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel
{
/// <summary>
/// Wraps an <see cref="XElement"/> that contains the XML-serialized representation of an
/// <see cref="IAuthenticatedEncryptorDescriptor"/> along with the type that can be used
/// to deserialize it.
/// </summary>
public sealed class XmlSerializedDescriptorInfo
{
/// <summary>
/// Creates an instance of an <see cref="XmlSerializedDescriptorInfo"/>.
/// </summary>
/// <param name="serializedDescriptorElement">The XML-serialized form of the <see cref="IAuthenticatedEncryptorDescriptor"/>.</param>
/// <param name="deserializerType">The class whose <see cref="IAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(XElement)"/>
/// method can be used to deserialize <paramref name="serializedDescriptorElement"/>.</param>
public XmlSerializedDescriptorInfo([NotNull] XElement serializedDescriptorElement, [NotNull] Type deserializerType)
{
if (!typeof(IAuthenticatedEncryptorDescriptorDeserializer).IsAssignableFrom(deserializerType))
{
throw new ArgumentException(
Resources.FormatTypeExtensions_BadCast(deserializerType.FullName, typeof(IAuthenticatedEncryptorDescriptorDeserializer).FullName),
nameof(deserializerType));
}
SerializedDescriptorElement = serializedDescriptorElement;
DeserializerType = deserializerType;
}
/// <summary>
/// The class whose <see cref="IAuthenticatedEncryptorDescriptorDeserializer.ImportFromXml(XElement)"/>
/// method can be used to deserialize the value stored in <see cref="SerializedDescriptorElement"/>.
/// </summary>
public Type DeserializerType { get; }
/// <summary>
/// An XML-serialized representation of an <see cref="IAuthenticatedEncryptorDescriptor"/>.
/// </summary>
public XElement SerializedDescriptorElement { get; }
}
}

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;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Specifies a symmetric encryption algorithm to use for providing confidentiality
/// to protected payloads.
/// </summary>
public enum EncryptionAlgorithm
{
/// <summary>
/// The AES algorithm (FIPS 197) with a 128-bit key running in Cipher Block Chaining mode.
/// </summary>
AES_128_CBC,
/// <summary>
/// The AES algorithm (FIPS 197) with a 192-bit key running in Cipher Block Chaining mode.
/// </summary>
AES_192_CBC,
/// <summary>
/// The AES algorithm (FIPS 197) with a 256-bit key running in Cipher Block Chaining mode.
/// </summary>
AES_256_CBC,
/// <summary>
/// The AES algorithm (FIPS 197) with a 128-bit key running in Galois/Counter Mode (FIPS SP 800-38D).
/// </summary>
/// <remarks>
/// This cipher mode produces a 128-bit authentication tag. This algorithm is currently only
/// supported on Windows.
/// </remarks>
AES_128_GCM,
/// <summary>
/// The AES algorithm (FIPS 197) with a 192-bit key running in Galois/Counter Mode (FIPS SP 800-38D).
/// </summary>
/// <remarks>
/// This cipher mode produces a 128-bit authentication tag.
/// </remarks>
AES_192_GCM,
/// <summary>
/// The AES algorithm (FIPS 197) with a 256-bit key running in Galois/Counter Mode (FIPS SP 800-38D).
/// </summary>
/// <remarks>
/// This cipher mode produces a 128-bit authentication tag.
/// </remarks>
AES_256_GCM,
}
}

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.Xml.Linq;
using Microsoft.AspNet.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.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

@ -1,21 +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.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

@ -1,21 +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.Xml.Linq;
namespace Microsoft.AspNet.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,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 Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Implemented by our options classes to generalize creating configuration objects.
/// </summary>
internal interface IInternalAuthenticatedEncryptionOptions
{
/// <summary>
/// Creates a <see cref="IInternalAuthenticatedEncryptorConfiguration"/> object
/// from the given options.
/// </summary>
IInternalAuthenticatedEncryptorConfiguration ToConfiguration();
/// <summary>
/// Performs a self-test of the algorithm specified by the options object.
/// </summary>
void Validate();
}
}

View File

@ -1,12 +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.DataProtection.AuthenticatedEncryption
{
internal interface IInternalConfigurationOptions
{
IAuthenticatedEncryptor CreateAuthenticatedEncryptor(ISecret secret);
}
}

View File

@ -0,0 +1,162 @@
// 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.Cryptography.Cng;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNet.DataProtection.Managed;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Options for configuring an authenticated encryption mechanism which uses
/// managed SymmetricAlgorithm and KeyedHashAlgorithm implementations.
/// </summary>
public sealed class ManagedAuthenticatedEncryptionOptions : IInternalAuthenticatedEncryptionOptions
{
/// <summary>
/// The type of the algorithm to use for symmetric encryption.
/// The type must subclass <see cref="SymmetricAlgorithm"/>.
/// 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>
[ApplyPolicy]
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>
[ApplyPolicy]
public int EncryptionAlgorithmKeySize { get; set; } = 256;
/// <summary>
/// The type of the algorithm to use for validation.
/// Type type must subclass <see cref="KeyedHashAlgorithm"/>.
/// 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>
[ApplyPolicy]
public Type ValidationAlgorithmType { get; set; } = typeof(HMACSHA256);
/// <summary>
/// Validates that this <see cref="ManagedAuthenticatedEncryptionOptions"/> is well-formed, i.e.,
/// that the specified algorithms actually exist and can be instantiated properly.
/// An exception will be thrown if validation fails.
/// </summary>
public void Validate()
{
// Run a sample payload through an encrypt -> decrypt operation to make sure data round-trips properly.
using (var encryptor = CreateAuthenticatedEncryptorInstance(Secret.Random(512 / 8)))
{
encryptor.PerformSelfTest();
}
}
/*
* HELPER ROUTINES
*/
internal ManagedAuthenticatedEncryptor CreateAuthenticatedEncryptorInstance(ISecret secret)
{
return new ManagedAuthenticatedEncryptor(
keyDerivationKey: new Secret(secret),
symmetricAlgorithmFactory: GetSymmetricBlockCipherAlgorithmFactory(),
symmetricAlgorithmKeySizeInBytes: EncryptionAlgorithmKeySize / 8,
validationAlgorithmFactory: GetKeyedHashAlgorithmFactory());
}
private Func<KeyedHashAlgorithm> GetKeyedHashAlgorithmFactory()
{
// basic argument checking
if (ValidationAlgorithmType == null)
{
throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(ValidationAlgorithmType));
}
if (ValidationAlgorithmType == typeof(HMACSHA256))
{
return () => new HMACSHA256();
}
else if (ValidationAlgorithmType == typeof(HMACSHA512))
{
return () => new HMACSHA512();
}
else
{
return AlgorithmActivator.CreateFactory<KeyedHashAlgorithm>(ValidationAlgorithmType);
}
}
private Func<SymmetricAlgorithm> GetSymmetricBlockCipherAlgorithmFactory()
{
// basic argument checking
if (EncryptionAlgorithmType == null)
{
throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithmType));
}
typeof(SymmetricAlgorithm).AssertIsAssignableFrom(EncryptionAlgorithmType);
if (EncryptionAlgorithmKeySize < 0)
{
throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize));
}
if (EncryptionAlgorithmType == typeof(Aes))
{
Func<Aes> factory = null;
#if !DNXCORE50
if (OSVersionUtil.IsWindows())
{
// If we're on desktop CLR and running on Windows, use the FIPS-compliant implementation.
factory = () => new AesCryptoServiceProvider();
}
#endif
return factory ?? Aes.Create;
}
else
{
return AlgorithmActivator.CreateFactory<SymmetricAlgorithm>(EncryptionAlgorithmType);
}
}
IInternalAuthenticatedEncryptorConfiguration IInternalAuthenticatedEncryptionOptions.ToConfiguration()
{
return new ManagedAuthenticatedEncryptorConfiguration(this);
}
/// <summary>
/// Contains helper methods for generating cryptographic algorithm factories.
/// </summary>
private static class AlgorithmActivator
{
/// <summary>
/// Creates a factory that wraps a call to <see cref="Activator.CreateInstance{T}"/>.
/// </summary>
public static Func<T> CreateFactory<T>(Type implementation)
{
return ((IActivator<T>)Activator.CreateInstance(typeof(AlgorithmActivatorCore<>).MakeGenericType(implementation))).Creator;
}
private interface IActivator<out T>
{
Func<T> Creator { get; }
}
private class AlgorithmActivatorCore<T> : IActivator<T> where T : new()
{
public Func<T> Creator { get; } = Activator.CreateInstance<T>;
}
}
}
}

View File

@ -1,74 +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.Xml.Linq;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.DataProtection.XmlEncryption;
namespace Microsoft.AspNet.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

@ -1,37 +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.AspNet.DataProtection.Managed;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
public sealed class ManagedAuthenticatedEncryptorConfigurationFactory : IAuthenticatedEncryptorConfigurationFactory
{
private readonly ManagedAuthenticatedEncryptorConfigurationOptions _options;
public ManagedAuthenticatedEncryptorConfigurationFactory([NotNull] IOptions<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);
Secret secret;
try
{
secret = new Secret(kdk);
}
finally
{
Array.Clear(kdk, 0, kdk.Length);
}
return new ManagedAuthenticatedEncryptorConfiguration(_options, secret);
}
}
}

View File

@ -1,121 +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.Reflection;
using System.Security.Cryptography;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.DataProtection.Managed;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Options for configuring an authenticated encryption mechanism which uses
/// managed SymmetricAlgorithm and KeyedHashAlgorithm implementations.
/// </summary>
public sealed class ManagedAuthenticatedEncryptorConfigurationOptions : IInternalConfigurationOptions
{
/// <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 Secret(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;
}
IAuthenticatedEncryptor IInternalConfigurationOptions.CreateAuthenticatedEncryptor(ISecret secret)
{
return CreateAuthenticatedEncryptor(secret);
}
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

@ -1,66 +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.Linq;
using System.Xml.Linq;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.DataProtection.XmlEncryption;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.DataProtection.AuthenticatedEncryption
{
internal sealed class ManagedAuthenticatedEncryptorConfigurationXmlReader : IAuthenticatedEncryptorConfigurationXmlReader
{
private readonly IServiceProvider _serviceProvider;
public ManagedAuthenticatedEncryptorConfigurationXmlReader(
[NotNull] IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
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)ActivatorUtilities.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 secret = new Secret(decryptedSecretBytes);
return new ManagedAuthenticatedEncryptorConfiguration(options, secret);
}
finally
{
Array.Clear(decryptedSecretBytes, 0, decryptedSecretBytes.Length);
}
}
}
}

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.DataProtection.AuthenticatedEncryption
{
/// <summary>
/// Specifies a message authentication algorithm to use for providing tamper-proofing
/// to protected payloads.
/// </summary>
public enum ValidationAlgorithm
{
/// <summary>
/// The HMAC algorithm (RFC 2104) using the SHA-256 hash function (FIPS 180-4).
/// </summary>
HMACSHA256,
/// <summary>
/// The HMAC algorithm (RFC 2104) using the SHA-512 hash function (FIPS 180-4).
/// </summary>
HMACSHA512,
}
}

View File

@ -5,6 +5,7 @@ using System;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.Cryptography.Cng;
using Microsoft.AspNet.Cryptography.SafeHandles;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.DataProtection.SP800_108;
namespace Microsoft.AspNet.DataProtection.Cng
@ -25,13 +26,6 @@ namespace Microsoft.AspNet.DataProtection.Cng
// 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;
@ -44,9 +38,6 @@ namespace Microsoft.AspNet.DataProtection.Cng
public CbcAuthenticatedEncryptor(Secret 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;
@ -56,14 +47,10 @@ namespace Microsoft.AspNet.DataProtection.Cng
_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");
// Argument checking on the algorithms and lengths passed in to us
AlgorithmAssert.IsAllowableSymmetricAlgorithmBlockSize(checked(_symmetricAlgorithmBlockSizeInBytes * 8));
AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked(_symmetricAlgorithmSubkeyLengthInBytes * 8));
AlgorithmAssert.IsAllowableValidationAlgorithmDigestSize(checked(_hmacAlgorithmDigestLengthInBytes * 8));
_contextHeader = CreateContextHeader();
}

View File

@ -23,6 +23,22 @@ namespace Microsoft.AspNet.DataProtection.Cng
private static readonly byte[] _purpose = Encoding.UTF8.GetBytes("DPAPI-Protected Secret");
// Probes to see if protecting to the current Windows user account is available.
// In theory this should never fail if the user profile is available, so it's more a defense-in-depth check.
public static bool CanProtectToCurrentUserAccount()
{
try
{
Guid dummy;
ProtectWithDpapi(new Secret((byte*)&dummy, sizeof(Guid)), protectToLocalMachine: false);
return true;
}
catch
{
return false;
}
}
public static byte[] ProtectWithDpapi(ISecret secret, bool protectToLocalMachine = false)
{
Debug.Assert(secret != null);
@ -35,7 +51,7 @@ namespace Microsoft.AspNet.DataProtection.Cng
secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
fixed (byte* pbPurpose = _purpose)
{
return ProtectWithDpapiImpl(pbPlaintextSecret, (uint)plaintextSecret.Length, pbPurpose, (uint)_purpose.Length, fLocalMachine: protectToLocalMachine);
return ProtectWithDpapiCore(pbPlaintextSecret, (uint)plaintextSecret.Length, pbPurpose, (uint)_purpose.Length, fLocalMachine: protectToLocalMachine);
}
}
finally
@ -46,7 +62,7 @@ namespace Microsoft.AspNet.DataProtection.Cng
}
}
internal static byte[] ProtectWithDpapiImpl(byte* pbSecret, uint cbSecret, byte* pbOptionalEntropy, uint cbOptionalEntropy, bool fLocalMachine = false)
internal static byte[] ProtectWithDpapiCore(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
@ -110,7 +126,7 @@ namespace Microsoft.AspNet.DataProtection.Cng
secret.WriteSecretIntoBuffer(new ArraySegment<byte>(plaintextSecret));
byte dummy; // used to provide a valid memory address if secret is zero-length
return ProtectWithDpapiNGImpl(
return ProtectWithDpapiNGCore(
protectionDescriptorHandle: protectionDescriptorHandle,
pbData: (pbPlaintextSecret != null) ? pbPlaintextSecret : &dummy,
cbData: (uint)plaintextSecret.Length);
@ -123,7 +139,7 @@ namespace Microsoft.AspNet.DataProtection.Cng
}
}
private static byte[] ProtectWithDpapiNGImpl(NCryptDescriptorHandle protectionDescriptorHandle, byte* pbData, uint cbData)
private static byte[] ProtectWithDpapiNGCore(NCryptDescriptorHandle protectionDescriptorHandle, byte* pbData, uint cbData)
{
Debug.Assert(protectionDescriptorHandle != null);
Debug.Assert(pbData != null);
@ -141,7 +157,7 @@ namespace Microsoft.AspNet.DataProtection.Cng
ppbProtectedBlob: out protectedData,
pcbProtectedBlob: out cbProtectedData);
UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus);
CryptoUtil.Assert(protectedData != null && !protectedData.IsInvalid, "protectedData != null && !protectedData.IsInvalid");
CryptoUtil.AssertSafeHandleIsValid(protectedData);
// Copy the data from LocalAlloc-allocated memory into a managed memory buffer.
using (protectedData)
@ -181,12 +197,12 @@ namespace Microsoft.AspNet.DataProtection.Cng
{
fixed (byte* pbPurpose = _purpose)
{
return UnprotectWithDpapiImpl(pbProtectedSecret, (uint)protectedSecret.Length, pbPurpose, (uint)_purpose.Length);
return UnprotectWithDpapiCore(pbProtectedSecret, (uint)protectedSecret.Length, pbPurpose, (uint)_purpose.Length);
}
}
}
internal static Secret UnprotectWithDpapiImpl(byte* pbProtectedData, uint cbProtectedData, byte* pbOptionalEntropy, uint cbOptionalEntropy)
internal static Secret UnprotectWithDpapiCore(byte* pbProtectedData, uint cbProtectedData, byte* pbOptionalEntropy, uint cbOptionalEntropy)
{
byte dummy; // provides a valid memory address if the secret or entropy has zero length
@ -242,13 +258,13 @@ namespace Microsoft.AspNet.DataProtection.Cng
fixed (byte* pbProtectedData = protectedData)
{
byte dummy; // used to provide a valid memory address if protected data is zero-length
return UnprotectWithDpapiNGImpl(
return UnprotectWithDpapiNGCore(
pbData: (pbProtectedData != null) ? pbProtectedData : &dummy,
cbData: (uint)protectedData.Length);
}
}
private static Secret UnprotectWithDpapiNGImpl(byte* pbData, uint cbData)
private static Secret UnprotectWithDpapiNGCore(byte* pbData, uint cbData)
{
Debug.Assert(pbData != null);
@ -265,7 +281,7 @@ namespace Microsoft.AspNet.DataProtection.Cng
ppbData: out unencryptedPayloadHandle,
pcbData: out cbUnencryptedPayload);
UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus);
CryptoUtil.Assert(unencryptedPayloadHandle != null && !unencryptedPayloadHandle.IsInvalid, "unencryptedPayloadHandle != null && !unencryptedPayloadHandle.IsInvalid");
CryptoUtil.AssertSafeHandleIsValid(unencryptedPayloadHandle);
// 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
@ -293,5 +309,50 @@ namespace Microsoft.AspNet.DataProtection.Cng
}
}
}
public static string GetRuleFromDpapiNGProtectedPayload(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 GetRuleFromDpapiNGProtectedPayloadCore(
pbData: (pbProtectedData != null) ? pbProtectedData : &dummy,
cbData: (uint)protectedData.Length);
}
}
private static string GetRuleFromDpapiNGProtectedPayloadCore(byte* pbData, uint cbData)
{
// from ncryptprotect.h
const uint NCRYPT_UNPROTECT_NO_DECRYPT = 0x00000001;
NCryptDescriptorHandle descriptorHandle;
LocalAllocHandle unprotectedDataHandle;
uint cbUnprotectedData;
int ntstatus = UnsafeNativeMethods.NCryptUnprotectSecret(
phDescriptor: out descriptorHandle,
dwFlags: NCRYPT_UNPROTECT_NO_DECRYPT,
pbProtectedBlob: pbData,
cbProtectedBlob: cbData,
pMemPara: IntPtr.Zero,
hWnd: IntPtr.Zero,
ppbData: out unprotectedDataHandle,
pcbData: out cbUnprotectedData);
UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus);
CryptoUtil.AssertSafeHandleIsValid(descriptorHandle);
if (unprotectedDataHandle != null && !unprotectedDataHandle.IsInvalid)
{
// we don't care about this value
unprotectedDataHandle.Dispose();
}
using (descriptorHandle)
{
return descriptorHandle.GetProtectionDescriptorRuleString();
}
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using Microsoft.AspNet.Cryptography;
using Microsoft.AspNet.Cryptography.Cng;
using Microsoft.AspNet.Cryptography.SafeHandles;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.DataProtection.SP800_108;
namespace Microsoft.AspNet.DataProtection.Cng
@ -38,9 +39,10 @@ namespace Microsoft.AspNet.DataProtection.Cng
public GcmAuthenticatedEncryptor(Secret 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");
// Is the key size appropriate?
AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked(symmetricAlgorithmKeySizeInBytes * 8));
CryptoUtil.Assert(symmetricAlgorithmHandle.GetCipherBlockLength() == 128 / 8, "GCM requires a block cipher algorithm with a 128-bit block size.");
_genRandom = genRandom ?? BCryptGenRandomImpl.Instance;
_sp800_108_ctr_hmac_provider = SP800_108_CTR_HMACSHA512Util.CreateProvider(keyDerivationKey);
_symmetricAlgorithmHandle = symmetricAlgorithmHandle;

View File

@ -0,0 +1,372 @@
// 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.ComponentModel;
using System.IO;
using Microsoft.AspNet.DataProtection.AuthenticatedEncryption;
using Microsoft.AspNet.DataProtection.KeyManagement;
using Microsoft.AspNet.DataProtection.XmlEncryption;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
using Microsoft.Win32;
#if !DNXCORE50 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml
using System.Security.Cryptography.X509Certificates;
#endif
namespace Microsoft.AspNet.DataProtection
{
/// <summary>
/// Provides access to configuration for the data protection system, which allows the
/// developer to configure default cryptographic algorithms, key storage locations,
/// and the mechanism by which keys are protected at rest.
/// </summary>
/// <remarks>
/// <para>
/// If the developer changes the at-rest key protection mechanism, it is intended that
/// he also change the key storage location, and vice versa. For instance, a call to
/// <see cref="ProtectKeysWithCertificate(string)"/> should generally be accompanied by
/// a call to <see cref="PersistKeysToFileSystem(DirectoryInfo)"/>, or exceptions may
/// occur at runtime due to the data protection system not knowing where to persist keys.
/// </para>
/// <para>
/// Similarly, when a developer modifies the default protected payload cryptographic
/// algorithms, it is intended that he also select an explitiy key storage location.
/// A call to <see cref="UseCryptographicAlgorithms(AuthenticatedEncryptionOptions)"/>
/// should therefore generally be paired with a call to <see cref="PersistKeysToFileSystem(DirectoryInfo)"/>,
/// for example.
/// </para>
/// <para>
/// When the default cryptographic algorithms or at-rest key protection mechanisms are
/// changed, they only affect <strong>new</strong> keys in the repository. The repository may
/// contain existing keys that use older algorithms or protection mechanisms.
/// </para>
/// </remarks>
public class DataProtectionConfiguration
{
/// <summary>
/// Creates a new configuration object linked to a <see cref="IServiceCollection"/>.
/// </summary>
public DataProtectionConfiguration([NotNull] IServiceCollection services)
{
Services = services;
}
/// <summary>
/// Provides access to the <see cref="IServiceCollection"/> passed to this object's constructor.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
/// <summary>
/// Registers a <see cref="IKeyEscrowSink"/> to perform escrow before keys are persisted to storage.
/// </summary>
/// <param name="sink">The instance of the <see cref="IKeyEscrowSink"/> to register.</param>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// Registrations are additive.
/// </remarks>
public DataProtectionConfiguration AddKeyEscrowSink([NotNull] IKeyEscrowSink sink)
{
Services.AddInstance<IKeyEscrowSink>(sink);
return this;
}
/// <summary>
/// Registers a <see cref="IKeyEscrowSink"/> to perform escrow before keys are persisted to storage.
/// </summary>
/// <typeparam name="TImplementation">The concrete type of the <see cref="IKeyEscrowSink"/> to register.</typeparam>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// Registrations are additive.
/// </remarks>
public DataProtectionConfiguration AddKeyEscrowSink<TImplementation>()
where TImplementation : IKeyEscrowSink
{
Services.AddSingleton<IKeyEscrowSink, TImplementation>();
return this;
}
/// <summary>
/// Registers a <see cref="IKeyEscrowSink"/> to perform escrow before keys are persisted to storage.
/// </summary>
/// <param name="factory">A factory that creates the <see cref="IKeyEscrowSink"/> instance.</param>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// Registrations are additive.
/// </remarks>
public DataProtectionConfiguration AddKeyEscrowSink([NotNull] Func<IServiceProvider, IKeyEscrowSink> factory)
{
Services.AddSingleton<IKeyEscrowSink>(factory);
return this;
}
/// <summary>
/// Configures miscellaneous global options.
/// </summary>
/// <param name="setupAction">A callback that configures the global options.</param>
/// <returns>The 'this' instance.</returns>
public DataProtectionConfiguration ConfigureGlobalOptions([NotNull] Action<DataProtectionOptions> setupAction)
{
Services.Configure(setupAction);
return this;
}
/// <summary>
/// Configures the data protection system to persist keys to the specified directory.
/// This path may be on the local machine or may point to a UNC share.
/// </summary>
/// <param name="directory">The directory in which to store keys.</param>
/// <returns>The 'this' instance.</returns>
public DataProtectionConfiguration PersistKeysToFileSystem([NotNull] DirectoryInfo directory)
{
Use(DataProtectionServiceDescriptors.IXmlRepository_FileSystem(directory));
return this;
}
/// <summary>
/// Configures the data protection system to persist keys to the Windows registry.
/// </summary>
/// <param name="registryKey">The location in the registry where keys should be stored.</param>
/// <returns>The 'this' instance.</returns>
public DataProtectionConfiguration PersistKeysToRegistry([NotNull] RegistryKey registryKey)
{
Use(DataProtectionServiceDescriptors.IXmlRepository_Registry(registryKey));
return this;
}
#if !DNXCORE50 // [[ISSUE60]] Remove this #ifdef when Core CLR gets support for EncryptedXml
/// <summary>
/// Configures keys to be encrypted to a given certificate before being persisted to storage.
/// </summary>
/// <param name="certificate">The certificate to use when encrypting keys.</param>
/// <returns>The 'this' instance.</returns>
public DataProtectionConfiguration ProtectKeysWithCertificate([NotNull] X509Certificate2 certificate)
{
Use(DataProtectionServiceDescriptors.IXmlEncryptor_Certificate(certificate));
return this;
}
/// <summary>
/// Configures keys to be encrypted to a given certificate before being persisted to storage.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate to use when encrypting keys.</param>
/// <returns>The 'this' instance.</returns>
public DataProtectionConfiguration ProtectKeysWithCertificate([NotNull] string thumbprint)
{
// Make sure the thumbprint corresponds to a valid certificate.
if (new CertificateResolver().ResolveCertificate(thumbprint) == null)
{
throw Error.CertificateXmlEncryptor_CertificateNotFound(thumbprint);
}
// ICertificateResolver is necessary for this type to work correctly, so register it
// if it doesn't already exist.
Services.TryAdd(DataProtectionServiceDescriptors.ICertificateResolver_Default());
Use(DataProtectionServiceDescriptors.IXmlEncryptor_Certificate(thumbprint));
return this;
}
#endif
/// <summary>
/// Configures keys to be encrypted with Windows DPAPI before being persisted to
/// storage. The encrypted key will only be decryptable by the current Windows user account.
/// </summary>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// This API is only supported on Windows platforms.
/// </remarks>
public DataProtectionConfiguration ProtectKeysWithDpapi()
{
return ProtectKeysWithDpapi(protectToLocalMachine: false);
}
/// <summary>
/// Configures keys to be encrypted with Windows DPAPI before being persisted to
/// storage.
/// </summary>
/// <param name="protectToLocalMachine">'true' if the key should be decryptable by any
/// use on the local machine, 'false' if the key should only be decryptable by the current
/// Windows user account.</param>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// This API is only supported on Windows platforms.
/// </remarks>
public DataProtectionConfiguration ProtectKeysWithDpapi(bool protectToLocalMachine)
{
Use(DataProtectionServiceDescriptors.IXmlEncryptor_Dpapi(protectToLocalMachine));
return this;
}
/// <summary>
/// Configures keys to be encrypted with Windows CNG DPAPI before being persisted
/// to storage. The keys will be decryptable by the current Windows user account.
/// </summary>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/hh706794(v=vs.85).aspx
/// for more information on DPAPI-NG. This API is only supported on Windows 8 / Windows Server 2012 and higher.
/// </remarks>
public DataProtectionConfiguration ProtectKeysWithDpapiNG()
{
return ProtectKeysWithDpapiNG(
protectionDescriptorRule: DpapiNGXmlEncryptor.GetDefaultProtectionDescriptorString(),
flags: DpapiNGProtectionDescriptorFlags.None);
}
/// <summary>
/// Configures keys to be encrypted with Windows CNG DPAPI before being persisted to storage.
/// </summary>
/// <param name="protectionDescriptorRule">The descriptor rule string with which to protect the key material.</param>
/// <param name="flags">Flags that should be passed to the call to 'NCryptCreateProtectionDescriptor'.
/// The default value of this parameter is <see cref="DpapiNGProtectionDescriptorFlags.None"/>.</param>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/hh769091(v=vs.85).aspx
/// and https://msdn.microsoft.com/en-us/library/windows/desktop/hh706800(v=vs.85).aspx
/// for more information on valid values for the the <paramref name="descriptor"/>
/// and <paramref name="flags"/> arguments.
/// This API is only supported on Windows 8 / Windows Server 2012 and higher.
/// </remarks>
public DataProtectionConfiguration ProtectKeysWithDpapiNG([NotNull] string protectionDescriptorRule, DpapiNGProtectionDescriptorFlags flags)
{
Use(DataProtectionServiceDescriptors.IXmlEncryptor_DpapiNG(protectionDescriptorRule, flags));
return this;
}
/// <summary>
/// Sets the default lifetime of keys created by the data protection system.
/// </summary>
/// <param name="lifetime">The lifetime (time before expiration) for newly-created keys.
/// See <see cref="KeyLifetimeOptions.NewKeyLifetime"/> for more information and
/// usage notes.</param>
/// <returns>The 'this' instance.</returns>
public DataProtectionConfiguration SetDefaultKeyLifetime(TimeSpan lifetime)
{
Services.Configure<KeyLifetimeOptions>(options =>
{
options.NewKeyLifetime = lifetime;
});
return this;
}
/// <summary>
/// Configures the data protection system to persist keys in storage as plaintext.
/// </summary>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// Caution: cryptographic key material will not be protected at rest.
/// </remarks>
public DataProtectionConfiguration SuppressProtectionOfKeysAtRest()
{
RemoveAllServicesOfType(typeof(IXmlEncryptor));
return this;
}
/// <summary>
/// Configures the data protection system to use the specified cryptographic algorithms
/// by default when generating protected payloads.
/// </summary>
/// <param name="options">Information about what cryptographic algorithms should be used.</param>
/// <returns>The 'this' instance.</returns>
public DataProtectionConfiguration UseCryptographicAlgorithms([NotNull] AuthenticatedEncryptionOptions options)
{
return UseCryptographicAlgorithmsCore(options);
}
/// <summary>
/// Configures the data protection system to use custom Windows CNG algorithms.
/// This API is intended for advanced scenarios where the developer cannot use the
/// algorithms specified in the <see cref="EncryptionAlgorithm"/> and
/// <see cref="ValidationAlgorithm"/> enumerations.
/// </summary>
/// <param name="options">Information about what cryptographic algorithms should be used.</param>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// This API is only available on Windows.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public DataProtectionConfiguration UseCustomCryptographicAlgorithms([NotNull] CngCbcAuthenticatedEncryptionOptions options)
{
return UseCryptographicAlgorithmsCore(options);
}
/// <summary>
/// Configures the data protection system to use custom Windows CNG algorithms.
/// This API is intended for advanced scenarios where the developer cannot use the
/// algorithms specified in the <see cref="EncryptionAlgorithm"/> and
/// <see cref="ValidationAlgorithm"/> enumerations.
/// </summary>
/// <param name="options">Information about what cryptographic algorithms should be used.</param>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// This API is only available on Windows.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public DataProtectionConfiguration UseCustomCryptographicAlgorithms([NotNull] CngGcmAuthenticatedEncryptionOptions options)
{
return UseCryptographicAlgorithmsCore(options);
}
/// <summary>
/// Configures the data protection system to use custom algorithms.
/// This API is intended for advanced scenarios where the developer cannot use the
/// algorithms specified in the <see cref="EncryptionAlgorithm"/> and
/// <see cref="ValidationAlgorithm"/> enumerations.
/// </summary>
/// <param name="options">Information about what cryptographic algorithms should be used.</param>
/// <returns>The 'this' instance.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public DataProtectionConfiguration UseCustomCryptographicAlgorithms([NotNull] ManagedAuthenticatedEncryptionOptions options)
{
return UseCryptographicAlgorithmsCore(options);
}
private DataProtectionConfiguration UseCryptographicAlgorithmsCore(IInternalAuthenticatedEncryptionOptions options)
{
options.Validate(); // perform self-test
Use(DataProtectionServiceDescriptors.IAuthenticatedEncryptorConfiguration_FromOptions(options));
return this;
}
/// <summary>
/// Configures the data protection system to use the <see cref="EphemeralDataProtectionProvider"/>
/// for data protection services.
/// </summary>
/// <returns>The 'this' instance.</returns>
/// <remarks>
/// If this option is used, payloads protected by the data protection system will
/// be permanently undecipherable after the application exits.
/// </remarks>
public DataProtectionConfiguration UseEphemeralDataProtectionProvider()
{
Use(DataProtectionServiceDescriptors.IDataProtectionProvider_Ephemeral());
return this;
}
/*
* UTILITY ISERVICECOLLECTION METHODS
*/
private void RemoveAllServicesOfType(Type serviceType)
{
// We go backward since we're modifying the collection in-place.
for (int i = Services.Count - 1; i >= 0; i--)
{
if (Services[i]?.ServiceType == serviceType)
{
Services.RemoveAt(i);
}
}
}
private void Use(ServiceDescriptor descriptor)
{
RemoveAllServicesOfType(descriptor.ServiceType);
Services.Add(descriptor);
}
}
}

View File

@ -2,8 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.AspNet.Cryptography;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.DataProtection
{
@ -22,81 +21,5 @@ namespace Microsoft.AspNet.DataProtection
return (protector as ITimeLimitedDataProtector)
?? new TimeLimitedDataProtector(protector.CreateProtector(TimeLimitedDataProtector.PurposeString));
}
/// <summary>
/// Creates an IDataProtector given an array of purposes.
/// </summary>
/// <param name="provider">The provider from which to generate the purpose chain.</param>
/// <param name="purposes">
/// This is a convenience method used for chaining several purposes together
/// in a single call to CreateProtector. See the documentation of
/// IDataProtectionProvider.CreateProtector for more information.
/// </param>
/// <returns>An IDataProtector tied to the provided purpose chain.</returns>
public static IDataProtector CreateProtector([NotNull] this IDataProtectionProvider provider, params string[] purposes)
{
if (purposes == null || purposes.Length == 0)
{
throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesArray, nameof(purposes));
}
IDataProtectionProvider retVal = provider;
foreach (string purpose in purposes)
{
if (String.IsNullOrEmpty(purpose))
{
throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesArray, nameof(purposes));
}
retVal = retVal.CreateProtector(purpose) ?? CryptoUtil.Fail<IDataProtector>("CreateProtector returned null.");
}
Debug.Assert(retVal is IDataProtector); // CreateProtector is supposed to return an instance of this interface
return (IDataProtector)retVal;
}
/// <summary>
/// Cryptographically protects a piece of plaintext data.
/// </summary>
/// <param name="protector">The data protector to use for this operation.</param>
/// <param name="unprotectedData">The plaintext data to protect.</param>
/// <returns>The protected form of the plaintext data.</returns>
public static string Protect([NotNull] this IDataProtector protector, [NotNull] string unprotectedData)
{
try
{
byte[] unprotectedDataAsBytes = EncodingUtil.SecureUtf8Encoding.GetBytes(unprotectedData);
byte[] protectedDataAsBytes = protector.Protect(unprotectedDataAsBytes);
return WebEncoders.Base64UrlEncode(protectedDataAsBytes);
}
catch (Exception ex) when (ex.RequiresHomogenization())
{
// Homogenize exceptions to CryptographicException
throw Error.CryptCommon_GenericError(ex);
}
}
/// <summary>
/// Cryptographically unprotects a piece of protected data.
/// </summary>
/// <param name="protector">The data protector to use for this operation.</param>
/// <param name="protectedData">The protected data to unprotect.</param>
/// <returns>The plaintext form of the protected data.</returns>
/// <remarks>
/// This method will throw CryptographicException if the input is invalid or malformed.
/// </remarks>
public static string Unprotect([NotNull] this IDataProtector protector, [NotNull] string protectedData)
{
try
{
byte[] protectedDataAsBytes = WebEncoders.Base64UrlDecode(protectedData);
byte[] unprotectedDataAsBytes = protector.Unprotect(protectedDataAsBytes);
return EncodingUtil.SecureUtf8Encoding.GetString(unprotectedDataAsBytes);
}
catch (Exception ex) when (ex.RequiresHomogenization())
{
// Homogenize exceptions to CryptographicException
throw Error.CryptCommon_GenericError(ex);
}
}
}
}

View File

@ -5,8 +5,21 @@ using System;
namespace Microsoft.AspNet.DataProtection
{
/// <summary>
/// Provides global options for the Data Protection system.
/// </summary>
public class DataProtectionOptions
{
/// <summary>
/// An identifier that uniquely discriminates this application from all other
/// applications on the machine. The discriminator value is implicitly included
/// in all protected payloads generated by the data protection system to isolate
/// multiple logical applications that all happen to be using the same key material.
/// </summary>
/// <remarks>
/// If two different applications need to share protected payloads, they should
/// ensure that this property is set to the same value across both applications.
/// </remarks>
public string ApplicationDiscriminator { get; set; }
}
}

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