Imported Upstream version 4.0.0~alpha1

Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
This commit is contained in:
Jo Shields
2015-04-07 09:35:12 +01:00
parent 283343f570
commit 3c1f479b9d
22469 changed files with 2931443 additions and 869343 deletions

View File

@@ -0,0 +1,99 @@
//------------------------------------------------------------------------------
// <copyright file="AspNetCryptoServiceProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Web.Configuration;
// The central ASP.NET class for providing ICryptoService instances.
// Get an instance of this class via the static Instance property.
internal sealed class AspNetCryptoServiceProvider : ICryptoServiceProvider {
private static readonly Lazy<AspNetCryptoServiceProvider> _singleton = new Lazy<AspNetCryptoServiceProvider>(GetSingletonCryptoServiceProvider);
private readonly ICryptoAlgorithmFactory _cryptoAlgorithmFactory;
private readonly IDataProtectorFactory _dataProtectorFactory;
private readonly bool _isDataProtectorEnabled;
private KeyDerivationFunction _keyDerivationFunction;
private readonly MachineKeySection _machineKeySection;
private readonly IMasterKeyProvider _masterKeyProvider;
// This constructor is used only for testing purposes and by the singleton provider
// and should not otherwise be called during ASP.NET request processing.
internal AspNetCryptoServiceProvider(MachineKeySection machineKeySection = null, ICryptoAlgorithmFactory cryptoAlgorithmFactory = null, IMasterKeyProvider masterKeyProvider = null, IDataProtectorFactory dataProtectorFactory = null, KeyDerivationFunction keyDerivationFunction = null) {
_machineKeySection = machineKeySection;
_cryptoAlgorithmFactory = cryptoAlgorithmFactory;
_masterKeyProvider = masterKeyProvider;
_dataProtectorFactory = dataProtectorFactory;
_keyDerivationFunction = keyDerivationFunction;
// This CryptoServiceProvider is active if specified as such in the <system.web/machineKey> section
IsDefaultProvider = (machineKeySection != null && machineKeySection.CompatibilityMode >= MachineKeyCompatibilityMode.Framework45);
// The DataProtectorCryptoService is active if specified as such in config
_isDataProtectorEnabled = (machineKeySection != null && !String.IsNullOrWhiteSpace(machineKeySection.DataProtectorType));
}
internal static AspNetCryptoServiceProvider Instance {
get {
return _singleton.Value;
}
}
// Returns a value indicating whether this crypto service provider is the default
// provider for the current application.
internal bool IsDefaultProvider {
get;
private set;
}
public ICryptoService GetCryptoService(Purpose purpose, CryptoServiceOptions options = CryptoServiceOptions.None) {
ICryptoService cryptoService;
if (_isDataProtectorEnabled && options == CryptoServiceOptions.None) {
// We can only use DataProtector if it's configured and the caller didn't ask for any special behavior like cacheability
cryptoService = GetDataProtectorCryptoService(purpose);
}
else {
// Otherwise we fall back to using the <machineKey> algorithms for cryptography
cryptoService = GetNetFXCryptoService(purpose, options);
}
// always homogenize errors returned from the crypto service
return new HomogenizingCryptoServiceWrapper(cryptoService);
}
private DataProtectorCryptoService GetDataProtectorCryptoService(Purpose purpose) {
// just return the ICryptoService directly
return new DataProtectorCryptoService(_dataProtectorFactory, purpose);
}
private NetFXCryptoService GetNetFXCryptoService(Purpose purpose, CryptoServiceOptions options) {
// Extract the encryption and validation keys from the provided Purpose object
CryptographicKey encryptionKey = purpose.GetDerivedEncryptionKey(_masterKeyProvider, _keyDerivationFunction);
CryptographicKey validationKey = purpose.GetDerivedValidationKey(_masterKeyProvider, _keyDerivationFunction);
// and return the ICryptoService
// (predictable IV turned on if the caller requested cacheable output)
return new NetFXCryptoService(_cryptoAlgorithmFactory, encryptionKey, validationKey, predictableIV: (options == CryptoServiceOptions.CacheableOutput));
}
private static AspNetCryptoServiceProvider GetSingletonCryptoServiceProvider() {
// Provides all of the necessary dependencies for an application-level
// AspNetCryptoServiceProvider.
MachineKeySection machineKeySection = MachineKeySection.GetApplicationConfig();
return new AspNetCryptoServiceProvider(
machineKeySection: machineKeySection,
cryptoAlgorithmFactory: new MachineKeyCryptoAlgorithmFactory(machineKeySection),
masterKeyProvider: new MachineKeyMasterKeyProvider(machineKeySection),
dataProtectorFactory: new MachineKeyDataProtectorFactory(machineKeySection),
keyDerivationFunction: SP800_108.DeriveKey);
}
}
}

View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
// <copyright file="CryptoAlgorithms.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
// Utility class to provide the "one true way" of getting instances of
// cryptographic algorithms, like SymmetricAlgorithm and HashAlgorithm.
// From discussions with [....] and the crypto board, we should prefer
// the CNG implementations of algorithms, then the CAPI implementations,
// then finally managed implementations if there are no CNG / CAPI
// implementations. The CNG / CAPI implementations are preferred for
// expandability, FIPS-compliance, and performance.
//
// .NET Framework 4.5 allows us to make two core assumptions:
// - The built-in HMAC classes have been updated for FIPS compliance.
// - Since .NET 4.5 requires Windows Server 2008 or greater, we can
// assume that CNG is available on the box.
//
// Note that some algorithms (MD5, DES, etc.) aren't FIPS-compliant
// under any circumstance. Calling these methods when the OS is
// configured to allow only FIPS-compliant algorithms will result
// in an exception being thrown.
//
// The .NET Framework's built-in algorithms don't need to be created
// under the application impersonation context since they don't depend
// on the impersonated identity.
internal static class CryptoAlgorithms {
internal static Aes CreateAes() {
return new AesCryptoServiceProvider();
}
[SuppressMessage("Microsoft.Cryptographic.Standard", "CA5351:DESCannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")]
[Obsolete("DES is deprecated and MUST NOT be used by new features. Consider using AES instead.")]
internal static DES CreateDES() {
return new DESCryptoServiceProvider();
}
internal static HMACSHA1 CreateHMACSHA1() {
return new HMACSHA1();
}
internal static HMACSHA256 CreateHMACSHA256() {
return new HMACSHA256();
}
internal static HMACSHA384 CreateHMACSHA384() {
return new HMACSHA384();
}
internal static HMACSHA512 CreateHMACSHA512() {
return new HMACSHA512();
}
internal static HMACSHA512 CreateHMACSHA512(byte[] key) {
return new HMACSHA512(key);
}
[SuppressMessage("Microsoft.Cryptographic.Standard", "CA5350:MD5CannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")]
[Obsolete("MD5 is deprecated and MUST NOT be used by new features. Consider using a SHA-2 algorithm instead.")]
internal static MD5 CreateMD5() {
return new MD5Cng();
}
[SuppressMessage("Microsoft.Cryptographic.Standard", "CA5354:SHA1CannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")]
[Obsolete("SHA1 is deprecated and MUST NOT be used by new features. Consider using a SHA-2 algorithm instead.")]
internal static SHA1 CreateSHA1() {
return new SHA1Cng();
}
internal static SHA256 CreateSHA256() {
return new SHA256Cng();
}
[SuppressMessage("Microsoft.Cryptographic.Standard", "CA5353:TripleDESCannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")]
[Obsolete("3DES is deprecated and MUST NOT be used by new features. Consider using AES instead.")]
internal static TripleDES CreateTripleDES() {
return new TripleDESCryptoServiceProvider();
}
}
}

View File

@@ -0,0 +1,21 @@
//------------------------------------------------------------------------------
// <copyright file="CryptoServiceOptions.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
// Describes options that can configure an ICryptoService.
internal enum CryptoServiceOptions {
// [default] no special behavior needed
None = 0,
// the output of the Protect method will be cached, so the same plaintext should lead to the same ciphertext (no randomness)
CacheableOutput,
}
}

View File

@@ -0,0 +1,156 @@
//------------------------------------------------------------------------------
// <copyright file="CryptoUtil.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Web.Util;
// Contains helper methods for dealing with cryptographic operations.
internal static class CryptoUtil {
/// <summary>
/// Similar to Encoding.UTF8, but throws on invalid bytes. Useful for security routines where we need
/// strong guarantees that we're always producing valid UTF8 streams.
/// </summary>
public static readonly UTF8Encoding SecureUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
/// <summary>
/// Converts a byte array into its hexadecimal representation.
/// </summary>
/// <param name="data">The binary byte array.</param>
/// <returns>The hexadecimal (uppercase) equivalent of the byte array.</returns>
public static string BinaryToHex(byte[] data) {
if (data == null) {
return null;
}
char[] hex = new char[checked(data.Length * 2)];
for (int i = 0; i < data.Length; i++) {
byte thisByte = data[i];
hex[2 * i] = NibbleToHex((byte)(thisByte >> 4)); // high nibble
hex[2 * i + 1] = NibbleToHex((byte)(thisByte & 0xf)); // low nibble
}
return new string(hex);
}
// Determines if two buffer instances are equal, e.g. whether they contain the same payload. This method
// is written in such a manner that it should take the same amount of time to execute regardless of
// whether the result is success or failure. The modulus operation is intended to make the check take the
// same amount of time, even if the buffers are of different lengths.
//
// !! DO NOT CHANGE THIS METHOD WITHOUT SECURITY
[MethodImpl(MethodImplOptions.NoOptimization)]
public static bool BuffersAreEqual(byte[] buffer1, int buffer1Offset, int buffer1Count, byte[] buffer2, int buffer2Offset, int buffer2Count) {
Debug.ValidateArrayBounds(buffer1, buffer1Offset, buffer1Count);
Debug.ValidateArrayBounds(buffer2, buffer2Offset, buffer2Count);
bool success = (buffer1Count == buffer2Count); // can't possibly be successful if the buffers are of different lengths
for (int i = 0; i < buffer1Count; i++) {
success &= (buffer1[buffer1Offset + i] == buffer2[buffer2Offset + (i % buffer2Count)]);
}
return success;
}
/// <summary>
/// Computes the SHA256 hash of a given input.
/// </summary>
/// <param name="input">The input over which to compute the hash.</param>
/// <returns>The binary hash (32 bytes) of the input.</returns>
public static byte[] ComputeSHA256Hash(byte[] input) {
return ComputeSHA256Hash(input, 0, input.Length);
}
/// <summary>
/// Computes the SHA256 hash of a given segment in a buffer.
/// </summary>
/// <param name="buffer">The buffer over which to compute the hash.</param>
/// <param name="offset">The offset at which to begin computing the hash.</param>
/// <param name="count">The number of bytes in the buffer to include in the hash.</param>
/// <returns>The binary hash (32 bytes) of the buffer segment.</returns>
public static byte[] ComputeSHA256Hash(byte[] buffer, int offset, int count) {
Debug.ValidateArrayBounds(buffer, offset, count);
using (SHA256 sha256 = CryptoAlgorithms.CreateSHA256()) {
return sha256.ComputeHash(buffer, offset, count);
}
}
/// <summary>
/// Returns an IV that's based solely on the contents of a buffer; useful for generating
/// predictable IVs for ciphertexts that need to be cached. The output value is only
/// appropriate for use as an IV and must not be used for any other purpose.
/// </summary>
/// <remarks>This method uses an iterated unkeyed SHA256 to calculate the IV.</remarks>
/// <param name="buffer">The input buffer over which to calculate the IV.</param>
/// <param name="ivBitLength">The requested length (in bits) of the IV to generate.</param>
/// <returns>The calculated IV.</returns>
public static byte[] CreatePredictableIV(byte[] buffer, int ivBitLength) {
// Algorithm:
// T_0 = SHA256(buffer)
// T_n = SHA256(T_{n-1})
// output = T_0 || T_1 || ... || T_n (as many blocks as necessary to reach ivBitLength)
byte[] output = new byte[ivBitLength / 8];
int bytesCopied = 0;
int bytesRemaining = output.Length;
using (SHA256 sha256 = CryptoAlgorithms.CreateSHA256()) {
while (bytesRemaining > 0) {
byte[] hashed = sha256.ComputeHash(buffer);
int bytesToCopy = Math.Min(bytesRemaining, hashed.Length);
Buffer.BlockCopy(hashed, 0, output, bytesCopied, bytesToCopy);
bytesCopied += bytesToCopy;
bytesRemaining -= bytesToCopy;
buffer = hashed; // next iteration (if it occurs) will operate over the block just hashed
}
}
return output;
}
/// <summary>
/// Converts a hexadecimal string into its binary representation.
/// </summary>
/// <param name="data">The hex string.</param>
/// <returns>The byte array corresponding to the contents of the hex string,
/// or null if the input string is not a valid hex string.</returns>
public static byte[] HexToBinary(string data) {
if (data == null || data.Length % 2 != 0) {
// input string length is not evenly divisible by 2
return null;
}
byte[] binary = new byte[data.Length / 2];
for (int i = 0; i < binary.Length; i++) {
int highNibble = HttpEncoderUtility.HexToInt(data[2 * i]);
int lowNibble = HttpEncoderUtility.HexToInt(data[2 * i + 1]);
if (highNibble == -1 || lowNibble == -1) {
return null; // bad hex data
}
binary[i] = (byte)((highNibble << 4) | lowNibble);
}
return binary;
}
// converts a nibble (4 bits) to its uppercase hexadecimal character representation [0-9, A-F]
private static char NibbleToHex(byte nibble) {
return (char)((nibble < 10) ? (nibble + '0') : (nibble - 10 + 'A'));
}
}
}

View File

@@ -0,0 +1,51 @@
//------------------------------------------------------------------------------
// <copyright file="CryptographicKey.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Web.Util;
// Represents a key that can be used for a cryptographic operation.
internal sealed class CryptographicKey {
private readonly byte[] _keyMaterial;
public CryptographicKey(byte[] keyMaterial) {
_keyMaterial = keyMaterial;
}
// Returns the length of the key (in bits).
public int KeyLength {
get {
return checked(_keyMaterial.Length * 8);
}
}
// Extracts the specified number of bits at the specified offset
// and returns a new CryptographicKey. This is not appropriate
// for subkey derivation, but it can be used if this cryptographic
// key is actually two keys (like encryption + validation)
// concatenated together. Inputs are specified as bit lengths.
public CryptographicKey ExtractBits(int offset, int count) {
Debug.Assert(offset % 8 == 0, "Offset must be divisible by 8.");
Debug.Assert(count % 8 == 0, "Count must be divisible by 8.");
int offsetBytes = offset / 8;
int countBytes = count / 8;
byte[] newKey = new byte[countBytes];
Buffer.BlockCopy(_keyMaterial, offsetBytes, newKey, 0, countBytes);
return new CryptographicKey(newKey);
}
// Returns the raw key material as a byte array.
public byte[] GetKeyMaterial() {
return _keyMaterial;
}
}
}

View File

@@ -0,0 +1,56 @@
//------------------------------------------------------------------------------
// <copyright file="DataProtectorCryptoService.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Security.Cryptography;
// Uses the DataProtector class to protect sensitive information
internal sealed class DataProtectorCryptoService : ICryptoService {
private readonly IDataProtectorFactory _dataProtectorFactory;
private readonly Purpose _purpose;
public DataProtectorCryptoService(IDataProtectorFactory dataProtectorFactory, Purpose purpose) {
_dataProtectorFactory = dataProtectorFactory;
_purpose = purpose;
}
// Wraps the common logic of working with a DataProtector instance.
// 'protect' is TRUE if we're calling Protect, FALSE if we're calling Unprotect.
private byte[] PerformOperation(byte[] data, bool protect) {
// Since the DataProtector might depend on the impersonated context, we must
// work with it only under app-level impersonation. The idea behind this is
// that if the cryptographic routine is provided by an OS-level implementation
// (like DPAPI), any keys will be locked to the account of the web application
// itself.
using (new ApplicationImpersonationContext()) {
DataProtector dataProtector = null;
try {
dataProtector = _dataProtectorFactory.GetDataProtector(_purpose);
return (protect) ? dataProtector.Protect(data) : dataProtector.Unprotect(data);
}
finally {
// These instances are transient
IDisposable disposable = dataProtector as IDisposable;
if (disposable != null) {
disposable.Dispose();
}
}
}
}
public byte[] Protect(byte[] clearData) {
return PerformOperation(clearData, protect: true);
}
public byte[] Unprotect(byte[] protectedData) {
return PerformOperation(protectedData, protect: false);
}
}
}

View File

@@ -0,0 +1,58 @@
//------------------------------------------------------------------------------
// <copyright file="HomogenizingCryptoServiceWrapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Configuration;
using System.Security.Cryptography;
// Wraps an ICryptoService instance and homogenizes any exceptions that might occur.
internal sealed class HomogenizingCryptoServiceWrapper : ICryptoService {
public HomogenizingCryptoServiceWrapper(ICryptoService wrapped) {
WrappedCryptoService = wrapped;
}
internal ICryptoService WrappedCryptoService {
get;
private set;
}
private static byte[] HomogenizeErrors(Func<byte[], byte[]> func, byte[] input) {
// If the underlying method returns null or throws an exception, the
// error will be homogenized as a single CryptographicException.
byte[] output = null;
bool allowExceptionToBubble = false;
try {
output = func(input);
return output;
}
catch (ConfigurationException) {
// ConfigurationException isn't a side channel; it means the application is misconfigured.
// We need to bubble this up so that the developer can react to it.
allowExceptionToBubble = true;
throw;
}
finally {
if (output == null && !allowExceptionToBubble) {
throw new CryptographicException();
}
}
}
public byte[] Protect(byte[] clearData) {
return HomogenizeErrors(WrappedCryptoService.Protect, clearData);
}
public byte[] Unprotect(byte[] protectedData) {
return HomogenizeErrors(WrappedCryptoService.Unprotect, protectedData);
}
}
}

View File

@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <copyright file="ICryptoAlgorithmFactory.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Security.Cryptography;
// Represents an object that can provide encryption + validation algorithm instances
internal interface ICryptoAlgorithmFactory {
// Gets a SymmetricAlgorithm instance that can be used for encryption / decryption
SymmetricAlgorithm GetEncryptionAlgorithm();
// Gets a KeyedHashAlgorithm instance that can be used for signing / validation
KeyedHashAlgorithm GetValidationAlgorithm();
}
}

View File

@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <copyright file="ICryptoService.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
// Represents an object that can perform cryptographic operations.
// Get an instance of this class via an ICryptoServiceProvider (like AspNetCryptoServiceProvider).
internal interface ICryptoService {
// Protects some data by applying appropriate cryptographic transformations to it.
byte[] Protect(byte[] clearData);
// Returns the unprotected form of some protected data by validating and undoing the cryptographic transformations that led to it.
byte[] Unprotect(byte[] protectedData);
}
}

View File

@@ -0,0 +1,18 @@
//------------------------------------------------------------------------------
// <copyright file="ICryptoServiceProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
// Represents an object that can provide ICryptoService instances.
// Get an instance of this type via the AspNetCryptoServiceProvider.Instance singleton property.
internal interface ICryptoServiceProvider {
ICryptoService GetCryptoService(Purpose purpose, CryptoServiceOptions options = CryptoServiceOptions.None);
}
}

View File

@@ -0,0 +1,18 @@
//------------------------------------------------------------------------------
// <copyright file="IDataProtectorFactory.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Security.Cryptography;
// Represents an object that can provide DataProtector instances
internal interface IDataProtectorFactory {
DataProtector GetDataProtector(Purpose purpose);
}
}

View File

@@ -0,0 +1,21 @@
//------------------------------------------------------------------------------
// <copyright file="IMasterKeyProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
// Represents an object that can provide master encryption / validation keys
internal interface IMasterKeyProvider {
// encryption + decryption key
CryptographicKey GetEncryptionKey();
// signing + validation key
CryptographicKey GetValidationKey();
}
}

View File

@@ -0,0 +1,15 @@
//------------------------------------------------------------------------------
// <copyright file="KeyDerivationFunction.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
// A delegate that represents a cryptographic key derivation function (KDF).
// A KDF takes a master key (the key derivation key) and a purpose string,
// producing a derived key in the process.
internal delegate CryptographicKey KeyDerivationFunction(CryptographicKey keyDerivationKey, Purpose purpose);
}

View File

@@ -0,0 +1,135 @@
//------------------------------------------------------------------------------
// <copyright file="MachineKeyCryptoAlgorithmFactory.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Security.Cryptography;
using System.Web.Configuration;
// Can create cryptographic algorithms from a given <machineKey> element
internal sealed class MachineKeyCryptoAlgorithmFactory : ICryptoAlgorithmFactory {
private Func<SymmetricAlgorithm> _encryptionAlgorithmFactory;
private readonly MachineKeySection _machineKeySection;
private Func<KeyedHashAlgorithm> _validationAlgorithmFactory;
public MachineKeyCryptoAlgorithmFactory(MachineKeySection machineKeySection) {
_machineKeySection = machineKeySection;
}
public SymmetricAlgorithm GetEncryptionAlgorithm() {
if (_encryptionAlgorithmFactory == null) {
_encryptionAlgorithmFactory = GetEncryptionAlgorithmFactory();
}
return _encryptionAlgorithmFactory();
}
private Func<SymmetricAlgorithm> GetEncryptionAlgorithmFactory() {
return GetGenericAlgorithmFactory<SymmetricAlgorithm>(
configAttributeName: "decryption",
configAttributeValue: _machineKeySection.GetDecryptionAttributeSkipValidation(),
switchStatement: algorithmName => {
// We suppress CS0618 since some of the algorithms we support are marked with [Obsolete].
// These deprecated algorithms are *not* enabled by default. Developers must opt-in to
// them, so we're secure by default.
#pragma warning disable 618
switch (algorithmName) {
case "AES":
case "Auto": // currently "Auto" defaults to AES
return CryptoAlgorithms.CreateAes;
case "DES":
return CryptoAlgorithms.CreateDES;
case "3DES":
return CryptoAlgorithms.CreateTripleDES;
default:
return null; // unknown
#pragma warning restore 618
}
},
errorResourceString: SR.Wrong_decryption_enum);
}
public KeyedHashAlgorithm GetValidationAlgorithm() {
if (_validationAlgorithmFactory == null) {
_validationAlgorithmFactory = GetValidationAlgorithmFactory();
}
return _validationAlgorithmFactory();
}
private Func<KeyedHashAlgorithm> GetValidationAlgorithmFactory() {
return GetGenericAlgorithmFactory<KeyedHashAlgorithm>(
configAttributeName: "validation",
configAttributeValue: _machineKeySection.GetValidationAttributeSkipValidation(),
switchStatement: algorithmName => {
switch (algorithmName) {
case "SHA1":
return CryptoAlgorithms.CreateHMACSHA1;
case "HMACSHA256":
return CryptoAlgorithms.CreateHMACSHA256;
case "HMACSHA384":
return CryptoAlgorithms.CreateHMACSHA384;
case "HMACSHA512":
return CryptoAlgorithms.CreateHMACSHA512;
default:
return null; // unknown
}
},
errorResourceString: SR.Wrong_validation_enum_FX45);
}
// Contains common logic for creating encryption / validation factories, including
// custom algorithm lookup and exception handling.
private Func<TResult> GetGenericAlgorithmFactory<TResult>(string configAttributeName, string configAttributeValue, Func<string, Func<TResult>> switchStatement, string errorResourceString) where TResult : class, IDisposable {
Func<TResult> factory;
if (configAttributeValue != null && configAttributeValue.StartsWith("alg:", StringComparison.Ordinal)) {
string algorithmName = configAttributeValue.Substring("alg:".Length);
factory = () => {
// Since the custom algorithm might depend on the impersonated
// identity, we must instantiate it under app-level impersonation.
using (new ApplicationImpersonationContext()) {
return (TResult)CryptoConfig.CreateFromName(algorithmName);
}
};
}
else {
// If using a built-in algorithm, consult the switch statement to get the factory.
factory = switchStatement(configAttributeValue);
}
// Invoke the factory once to make sure there aren't any configuration errors.
Exception factoryCreationException = null;
try {
if (factory != null) {
TResult algorithm = factory();
if (algorithm != null) {
algorithm.Dispose();
return factory; // we know at this point the factory is good
}
}
}
catch (Exception ex) {
factoryCreationException = ex;
}
// If we reached this point, there was a failure:
// the factory returned null, threw, or did something else unexpected.
throw ConfigUtil.MakeConfigurationErrorsException(
message: SR.GetString(errorResourceString),
innerException: factoryCreationException, // can be null
configProperty: _machineKeySection.ElementInformation.Properties[configAttributeName]);
}
}
}

View File

@@ -0,0 +1,69 @@
//------------------------------------------------------------------------------
// <copyright file="MachineKeyDataProtectorFactory.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Security.Cryptography;
using System.Web.Configuration;
// Can create DataProtector instances from a given <machineKey> element
internal sealed class MachineKeyDataProtectorFactory : IDataProtectorFactory {
private static readonly Purpose _creationTestingPurpose = new Purpose("test-1", "test-2", "test-3");
private Func<Purpose, DataProtector> _dataProtectorFactory;
private readonly MachineKeySection _machineKeySection;
public MachineKeyDataProtectorFactory(MachineKeySection machineKeySection) {
_machineKeySection = machineKeySection;
}
public DataProtector GetDataProtector(Purpose purpose) {
if (_dataProtectorFactory == null) {
_dataProtectorFactory = GetDataProtectorFactory();
}
return _dataProtectorFactory(purpose);
}
private Func<Purpose, DataProtector> GetDataProtectorFactory() {
string applicationName = _machineKeySection.ApplicationName;
string dataProtectorTypeName = _machineKeySection.DataProtectorType;
Func<Purpose, DataProtector> factory = purpose => {
// Since the custom implementation might depend on the impersonated
// identity, we must instantiate it under app-level impersonation.
using (new ApplicationImpersonationContext()) {
return DataProtector.Create(dataProtectorTypeName, applicationName, purpose.PrimaryPurpose, purpose.SpecificPurposes);
}
};
// Invoke the factory once to make sure there aren't any configuration errors.
Exception factoryCreationException = null;
try {
DataProtector dataProtector = factory(_creationTestingPurpose);
if (dataProtector != null) {
IDisposable disposable = dataProtector as IDisposable;
if (disposable != null) {
disposable.Dispose();
}
return factory; // we know at this point the factory is good
}
}
catch (Exception ex) {
factoryCreationException = ex;
}
// If we reached this point, there was a failure:
// the factory returned null, threw, or did something else unexpected.
throw ConfigUtil.MakeConfigurationErrorsException(
message: SR.GetString(SR.MachineKeyDataProtectorFactory_FactoryCreationFailed),
innerException: factoryCreationException, // can be null
configProperty: _machineKeySection.ElementInformation.Properties["dataProtectorType"]);
}
}
}

View File

@@ -0,0 +1,174 @@
//------------------------------------------------------------------------------
// <copyright file="MachineKeyMasterKeyProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web.Configuration;
// Gets this application's master keys from the <machineKey> element,
// optionally going against the auto-gen keys if AutoGenerate has been specified.
internal sealed class MachineKeyMasterKeyProvider : IMasterKeyProvider {
private const int AUTOGEN_ENCRYPTION_OFFSET = 0;
private const int AUTOGEN_ENCRYPTION_KEYLENGTH = 256; // AES-256
private const int AUTOGEN_VALIDATION_OFFSET = AUTOGEN_ENCRYPTION_KEYLENGTH;
private const int AUTOGEN_VALIDATION_KEYLENGTH = 256; // HMACSHA256
private const string AUTOGEN_KEYDERIVATION_PRIMARYPURPOSE = "MachineKeyDerivation";
private const string AUTOGEN_KEYDERIVATION_ISOLATEAPPS_SPECIFICPURPOSE = "IsolateApps";
private const string AUTOGEN_KEYDERIVATION_ISOLATEBYAPPID_SPECIFICPURPOSE = "IsolateByAppId";
private string _applicationId;
private string _applicationName;
private CryptographicKey _autogenKeys;
private CryptographicKey _encryptionKey;
private KeyDerivationFunction _keyDerivationFunction;
private readonly MachineKeySection _machineKeySection;
private CryptographicKey _validationKey;
// the only required parameter is 'machineKeySection'; other parameters are just used for unit testing
internal MachineKeyMasterKeyProvider(MachineKeySection machineKeySection, string applicationId = null, string applicationName = null, CryptographicKey autogenKeys = null, KeyDerivationFunction keyDerivationFunction = null) {
_machineKeySection = machineKeySection;
_applicationId = applicationId;
_applicationName = applicationName;
_autogenKeys = autogenKeys;
_keyDerivationFunction = keyDerivationFunction;
}
internal string ApplicationName {
get {
if (_applicationName == null) {
_applicationName = HttpRuntime.AppDomainAppVirtualPath ?? Process.GetCurrentProcess().MainModule.ModuleName;
}
return _applicationName;
}
}
internal string ApplicationId {
get {
if (_applicationId == null) {
_applicationId = HttpRuntime.AppDomainAppId;
}
return _applicationId;
}
}
internal CryptographicKey AutogenKeys {
get {
if (_autogenKeys == null) {
_autogenKeys = new CryptographicKey(HttpRuntime.s_autogenKeys);
}
return _autogenKeys;
}
}
internal KeyDerivationFunction KeyDerivationFunction {
get {
if (_keyDerivationFunction == null) {
_keyDerivationFunction = SP800_108.DeriveKey;
}
return _keyDerivationFunction;
}
}
private static void AddSpecificPurposeString(IList<string> specificPurposes, string key, string value) {
specificPurposes.Add(key + ": " + value);
}
// Generates 'cryptographicKey' from either the raw key material specified in config
// or from the auto-generated key found in the system registry, optionally performing
// subkey derivation.
private CryptographicKey GenerateCryptographicKey(string configAttributeName, string configAttributeValue, int autogenKeyOffset, int autogenKeyCount, string errorResourceString) {
byte[] keyMaterial = CryptoUtil.HexToBinary(configAttributeValue);
// If <machineKey> contained a valid key, just use it verbatim.
if (keyMaterial != null && keyMaterial.Length > 0) {
return new CryptographicKey(keyMaterial);
}
// Otherwise, we need to generate it.
bool autoGenerate = false;
bool isolateApps = false;
bool isolateByAppId = false;
if (configAttributeValue != null) {
foreach (string flag in configAttributeValue.Split(',')) {
switch (flag) {
case "AutoGenerate":
autoGenerate = true;
break;
case "IsolateApps":
isolateApps = true;
break;
case "IsolateByAppId":
isolateByAppId = true;
break;
default:
throw ConfigUtil.MakeConfigurationErrorsException(
message: SR.GetString(errorResourceString),
configProperty: _machineKeySection.ElementInformation.Properties[configAttributeName]);
}
}
}
if (!autoGenerate) {
// at the absolute minimum, we must be configured to autogenerate
throw ConfigUtil.MakeConfigurationErrorsException(
message: SR.GetString(errorResourceString),
configProperty: _machineKeySection.ElementInformation.Properties[configAttributeName]);
}
// The key should be a subset of the auto-generated key (which is a concatenation of several keys)
CryptographicKey keyDerivationKey = AutogenKeys.ExtractBits(autogenKeyOffset, autogenKeyCount);
List<string> specificPurposes = new List<string>();
if (isolateApps) {
// Use the application name to derive a new cryptographic key
AddSpecificPurposeString(specificPurposes, AUTOGEN_KEYDERIVATION_ISOLATEAPPS_SPECIFICPURPOSE, ApplicationName);
}
if (isolateByAppId) {
// Use the application ID to derive a new cryptographic key
AddSpecificPurposeString(specificPurposes, AUTOGEN_KEYDERIVATION_ISOLATEBYAPPID_SPECIFICPURPOSE, ApplicationId);
}
// Don't use the auto-gen key directly; derive a new one based on specified parameters.
Purpose purpose = new Purpose(AUTOGEN_KEYDERIVATION_PRIMARYPURPOSE, specificPurposes.ToArray());
return KeyDerivationFunction(keyDerivationKey, purpose);
}
public CryptographicKey GetEncryptionKey() {
if (_encryptionKey == null) {
_encryptionKey = GenerateCryptographicKey(
configAttributeName: "decryptionKey",
configAttributeValue: _machineKeySection.DecryptionKey,
autogenKeyOffset: AUTOGEN_ENCRYPTION_OFFSET,
autogenKeyCount: AUTOGEN_ENCRYPTION_KEYLENGTH,
errorResourceString: SR.Invalid_decryption_key);
}
return _encryptionKey;
}
public CryptographicKey GetValidationKey() {
if (_validationKey == null) {
_validationKey = GenerateCryptographicKey(
configAttributeName: "validationKey",
configAttributeValue: _machineKeySection.ValidationKey,
autogenKeyOffset: AUTOGEN_VALIDATION_OFFSET,
autogenKeyCount: AUTOGEN_VALIDATION_KEYLENGTH,
errorResourceString: SR.Invalid_validation_key);
}
return _validationKey;
}
}
}

View File

@@ -0,0 +1,186 @@
//------------------------------------------------------------------------------
// <copyright file="NetFXCryptoService.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.IO;
using System.Security.Cryptography;
/******************************************************************
* !! WARNING !! *
* This class contains cryptographic code. If you make changes to *
* this class, please have it reviewed by the appropriate people. *
******************************************************************/
// Uses .NET Framework classes to encrypt (SymmetricAlgorithm) and sign (KeyedHashAlgorithm) data.
//
// [PROTECT]
// INPUT: clearData
// OUTPUT: protectedData
// ALGORITHM:
// protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
//
// [UNPROTECT]
// INPUT: protectedData
// OUTPUT: clearData
// ALGORITHM:
// 1) Assume protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
// 2) Validate the signature over the payload and strip it from the end
// 3) Strip off the IV from the beginning of the payload
// 4) Decrypt what remains of the payload, and return it as clearData
internal sealed class NetFXCryptoService : ICryptoService {
private readonly ICryptoAlgorithmFactory _cryptoAlgorithmFactory;
private readonly CryptographicKey _encryptionKey;
private readonly bool _predictableIV;
private readonly CryptographicKey _validationKey;
public NetFXCryptoService(ICryptoAlgorithmFactory cryptoAlgorithmFactory, CryptographicKey encryptionKey, CryptographicKey validationKey, bool predictableIV = false) {
_cryptoAlgorithmFactory = cryptoAlgorithmFactory;
_encryptionKey = encryptionKey;
_validationKey = validationKey;
_predictableIV = predictableIV;
}
public byte[] Protect(byte[] clearData) {
// The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures.
checked {
// These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block.
using (SymmetricAlgorithm encryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
// Initialize the algorithm with the specified key and an appropriate IV
encryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial();
if (_predictableIV) {
// The caller wanted the output to be predictable (e.g. for caching), so we'll create an
// appropriate IV directly from the input buffer. The IV length is equal to the block size.
encryptionAlgorithm.IV = CryptoUtil.CreatePredictableIV(clearData, encryptionAlgorithm.BlockSize);
}
else {
// If the caller didn't ask for a predictable IV, just let the algorithm itself choose one.
encryptionAlgorithm.GenerateIV();
}
byte[] iv = encryptionAlgorithm.IV;
using (MemoryStream memStream = new MemoryStream()) {
memStream.Write(iv, 0, iv.Length);
// At this point:
// memStream := IV
// Write the encrypted payload to the memory stream.
using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor()) {
using (CryptoStream cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write)) {
cryptoStream.Write(clearData, 0, clearData.Length);
cryptoStream.FlushFinalBlock();
// At this point:
// memStream := IV || Enc(Kenc, IV, clearData)
// These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block.
using (KeyedHashAlgorithm signingAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) {
// Initialize the algorithm with the specified key
signingAlgorithm.Key = _validationKey.GetKeyMaterial();
// Compute the signature
byte[] signature = signingAlgorithm.ComputeHash(memStream.GetBuffer(), 0, (int)memStream.Length);
// At this point:
// memStream := IV || Enc(Kenc, IV, clearData)
// signature := Sign(Kval, IV || Enc(Kenc, IV, clearData))
// Append the signature to the encrypted payload
memStream.Write(signature, 0, signature.Length);
// At this point:
// memStream := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
// Algorithm complete
byte[] protectedData = memStream.ToArray();
return protectedData;
}
}
}
}
}
}
}
public byte[] Unprotect(byte[] protectedData) {
// The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures.
checked {
// We want to check that the input is in the form:
// protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
// Definitions used in this method:
// encryptedPayload := Enc(Kenc, IV, clearData)
// signature := Sign(Kval, IV || encryptedPayload)
// These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block.
using (SymmetricAlgorithm decryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
decryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial();
// These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block.
using (KeyedHashAlgorithm validationAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) {
validationAlgorithm.Key = _validationKey.GetKeyMaterial();
// First, we need to verify that protectedData is even long enough to contain
// the required components (IV, encryptedPayload, signature).
int ivByteCount = decryptionAlgorithm.BlockSize / 8; // IV length is equal to the block size
int signatureByteCount = validationAlgorithm.HashSize / 8;
int encryptedPayloadByteCount = protectedData.Length - ivByteCount - signatureByteCount;
if (encryptedPayloadByteCount <= 0) {
// protectedData doesn't meet minimum length requirements
return null;
}
// If that check passes, we need to detect payload tampering.
// Compute the signature over the IV and encrypted payload
// computedSignature := Sign(Kval, IV || encryptedPayload)
byte[] computedSignature = validationAlgorithm.ComputeHash(protectedData, 0, ivByteCount + encryptedPayloadByteCount);
if (!CryptoUtil.BuffersAreEqual(
buffer1: protectedData, buffer1Offset: ivByteCount + encryptedPayloadByteCount, buffer1Count: signatureByteCount,
buffer2: computedSignature, buffer2Offset: 0, buffer2Count: computedSignature.Length)) {
// the computed signature didn't match the incoming signature, which is a sign of payload tampering
return null;
}
// At this point, we're certain that we generated the signature over this payload,
// so we can go ahead with decryption.
// Populate the IV from the incoming stream
byte[] iv = new byte[ivByteCount];
Buffer.BlockCopy(protectedData, 0, iv, 0, iv.Length);
decryptionAlgorithm.IV = iv;
// Write the decrypted payload to the memory stream.
using (MemoryStream memStream = new MemoryStream()) {
using (ICryptoTransform decryptor = decryptionAlgorithm.CreateDecryptor()) {
using (CryptoStream cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Write)) {
cryptoStream.Write(protectedData, ivByteCount, encryptedPayloadByteCount);
cryptoStream.FlushFinalBlock();
// At this point
// memStream := clearData
byte[] clearData = memStream.ToArray();
return clearData;
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,166 @@
//------------------------------------------------------------------------------
// <copyright file="Purpose.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
// Represents a purpose that can be passed to a cryptographic routine to control key derivation / ciphertext modification.
// This is hardening the crypto routines to prevent playing ciphertext off of components that didn't generate them.
//
// !! IMPORTANT !!
// The built-in purposes do not contain privileged information and are not meant to be treated as secrets. Any external
// person can disassemble our code or look directly at our source to see what our Purpose objects are used for.
//
// PrimaryPurpose: This is a well-known string that identifies the reason for this Purpose. The pattern we use
// is that PrimaryPurpose is the name of the consumer, making each consumer's Purpose unique.
//
// SpecificPurposes: These are extra optional strings that further differentiate Purpose objects that might have the
// same PrimaryPurpose. The pattern we use is that if a single consumer has multiple Purposes, he can use
// SpecificPurposes to uniquely identify them. The information here is generally not secret (we can put the type of
// the currently executing Page here, for example), but it is valid to seed this property with a secret obtained
// at runtime (such as a nonce shared between two parties).
internal sealed class Purpose {
// predefined purposes
public static readonly Purpose AnonymousIdentificationModule_Ticket = new Purpose("AnonymousIdentificationModule.Ticket");
public static readonly Purpose AssemblyResourceLoader_WebResourceUrl = new Purpose("AssemblyResourceLoader.WebResourceUrl");
public static readonly Purpose FormsAuthentication_Ticket = new Purpose("FormsAuthentication.Ticket");
public static readonly Purpose WebForms_Page_PreviousPageID = new Purpose("WebForms.Page.PreviousPageID");
public static readonly Purpose RolePrincipal_Ticket = new Purpose("RolePrincipal.Ticket");
public static readonly Purpose ScriptResourceHandler_ScriptResourceUrl = new Purpose("ScriptResourceHandler.ScriptResourceUrl");
// predefined ViewState purposes; they won't be used as-is (they're combined with the page information)
public static readonly Purpose WebForms_ClientScriptManager_EventValidation = new Purpose("WebForms.ClientScriptManager.EventValidation");
public static readonly Purpose WebForms_DetailsView_KeyTable = new Purpose("WebForms.DetailsView.KeyTable");
public static readonly Purpose WebForms_GridView_DataKeys = new Purpose("WebForms.GridView.DataKeys");
public static readonly Purpose WebForms_GridView_SortExpression = new Purpose("WebForms.GridView.SortExpression");
public static readonly Purpose WebForms_HiddenFieldPageStatePersister_ClientState = new Purpose("WebForms.HiddenFieldPageStatePersister.ClientState");
public static readonly Purpose WebForms_ScriptManager_HistoryState = new Purpose("WebForms.ScriptManager.HistoryState");
public static readonly Purpose WebForms_SessionPageStatePersister_ClientState = new Purpose("WebForms.SessionPageStatePersister.ClientState");
// predefined miscellaneoous purposes; they won't be used as-is (they're combined with other specificPurposes)
public static readonly Purpose User_MachineKey_Protect = new Purpose("User.MachineKey.Protect"); // used by the MachineKey static class Protect / Unprotect methods
public static readonly Purpose User_ObjectStateFormatter_Serialize = new Purpose("User.ObjectStateFormatter.Serialize"); // used by ObjectStateFormatter.Serialize() if called manually
public readonly string PrimaryPurpose;
public readonly string[] SpecificPurposes;
private byte[] _derivedKeyLabel;
private byte[] _derivedKeyContext;
public Purpose(string primaryPurpose, params string[] specificPurposes)
: this(primaryPurpose, specificPurposes, null, null) {
}
// ctor for unit testing
internal Purpose(string primaryPurpose, string[] specificPurposes, CryptographicKey derivedEncryptionKey, CryptographicKey derivedValidationKey) {
PrimaryPurpose = primaryPurpose;
SpecificPurposes = specificPurposes ?? new string[0];
DerivedEncryptionKey = derivedEncryptionKey;
DerivedValidationKey = derivedValidationKey;
SaveDerivedKeys = (SpecificPurposes.Length == 0);
}
// The cryptographic keys that were derived from this Purpose.
internal CryptographicKey DerivedEncryptionKey { get; private set; }
internal CryptographicKey DerivedValidationKey { get; private set; }
// Whether the derived key should be saved back to this Purpose object by the ICryptoService,
// e.g. because this Purpose will be used over and over again. We assume that any built-in
// Purpose object that is passed without any specific purposes is intended for repeated use,
// hence the ICryptoService will try to cache cryptographic keys as a performance optimization.
// If specific purposes have been specified, they were likely generated at runtime, hence it
// is not appropriate for the keys to be cached in this instance.
internal bool SaveDerivedKeys { get; set; }
// Returns a new Purpose which is the specified Purpose plus the specified SpecificPurpose.
// Leaves the original Purpose unmodified.
internal Purpose AppendSpecificPurpose(string specificPurpose) {
// Append the specified specificPurpose to the existing list
string[] newSpecificPurposes = new string[SpecificPurposes.Length + 1];
Array.Copy(SpecificPurposes, newSpecificPurposes, SpecificPurposes.Length);
newSpecificPurposes[newSpecificPurposes.Length - 1] = specificPurpose;
return new Purpose(PrimaryPurpose, newSpecificPurposes);
}
// Returns a new Purpose which is the specified Purpose plus the specified SpecificPurposes.
// Leaves the original Purpose unmodified.
internal Purpose AppendSpecificPurposes(IList<string> specificPurposes) {
// No specific purposes to add
if (specificPurposes == null || specificPurposes.Count == 0) {
return this;
}
// Append the specified specificPurposes to the existing list
string[] newSpecificPurposes = new string[SpecificPurposes.Length + specificPurposes.Count];
Array.Copy(SpecificPurposes, newSpecificPurposes, SpecificPurposes.Length);
specificPurposes.CopyTo(newSpecificPurposes, SpecificPurposes.Length);
return new Purpose(PrimaryPurpose, newSpecificPurposes);
}
public CryptographicKey GetDerivedEncryptionKey(IMasterKeyProvider masterKeyProvider, KeyDerivationFunction keyDerivationFunction) {
// has a key already been stored?
CryptographicKey actualDerivedKey = DerivedEncryptionKey;
if (actualDerivedKey == null) {
CryptographicKey masterKey = masterKeyProvider.GetEncryptionKey();
actualDerivedKey = keyDerivationFunction(masterKey, this);
// only save the key back to storage if this Purpose is configured to do so
if (SaveDerivedKeys) {
DerivedEncryptionKey = actualDerivedKey;
}
}
return actualDerivedKey;
}
public CryptographicKey GetDerivedValidationKey(IMasterKeyProvider masterKeyProvider, KeyDerivationFunction keyDerivationFunction) {
// has a key already been stored?
CryptographicKey actualDerivedKey = DerivedValidationKey;
if (actualDerivedKey == null) {
CryptographicKey masterKey = masterKeyProvider.GetValidationKey();
actualDerivedKey = keyDerivationFunction(masterKey, this);
// only save the key back to storage if this Purpose is configured to do so
if (SaveDerivedKeys) {
DerivedValidationKey = actualDerivedKey;
}
}
return actualDerivedKey;
}
// Returns a label and context suitable for passing into the SP800-108 KDF.
internal void GetKeyDerivationParameters(out byte[] label, out byte[] context) {
// The primary purpose can just be used as the label directly, since ASP.NET
// is always in full control of the primary purpose (it's never user-specified).
if (_derivedKeyLabel == null) {
_derivedKeyLabel = CryptoUtil.SecureUTF8Encoding.GetBytes(PrimaryPurpose);
}
// The specific purposes (which can contain nonce, identity, etc.) are concatenated
// together to form the context. The BinaryWriter class prepends each element with
// a 7-bit encoded length to guarantee uniqueness.
if (_derivedKeyContext == null) {
using (MemoryStream stream = new MemoryStream())
using (BinaryWriter writer = new BinaryWriter(stream, CryptoUtil.SecureUTF8Encoding)) {
foreach (string specificPurpose in SpecificPurposes) {
writer.Write(specificPurpose);
}
_derivedKeyContext = stream.ToArray();
}
}
label = _derivedKeyLabel;
context = _derivedKeyContext;
}
}
}

View File

@@ -0,0 +1,119 @@
//------------------------------------------------------------------------------
// <copyright file="SP800_108.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Security.Cryptography {
using System;
using System.Security.Cryptography;
/******************************************************************
* !! WARNING !! *
* This class contains cryptographic code. If you make changes to *
* this class, please have it reviewed by the appropriate people. *
******************************************************************/
// Implements the NIST SP800-108 key derivation routine in counter mode with an HMAC PRF (HMACSHA512).
// See: http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf
//
// The algorithm is defined as follows:
//
// INPUTS:
// PRF = The pseudo-random function used for key derivation; in our case, an HMAC.
// KI = The key derivation key (master key) from which keys will be derived.
// Label = The purpose of the derived key.
// Context = Information related to the derived key, such as consuming party identities or a nonce.
// L = The desired length (in bits) of the derived key.
//
// ALGORITHM:
// Let n = ceil(L / HMAC-output-size)
// For i = 1 to n,
// K_i = PRF(KI, [i]_2 || Label || 0x00 || Context || [L]_2)
// where [x]_2 = the big-endian representation of 'x'
//
// OUTPUT:
// Result := K_1 || K_2 || ... || K_n, truncated to be L bits in length
internal static class SP800_108 {
// Implements the KeyDerivationFunction delegate signature; public entry point to the API.
public static CryptographicKey DeriveKey(CryptographicKey keyDerivationKey, Purpose purpose) {
// After consultation with the crypto board, we have decided to use HMACSHA512 as the PRF
// to our KDF. The reason for this is that our PRF is an HMAC, so the total entropy of the
// PRF is given by MIN(key derivation key length, HMAC block size). It is conceivable that
// a developer might specify a key greater than 256 bits in length, at which point using
// a shorter PRF like HMACSHA256 starts discarding entropy. But from the crypto team's
// perspective it is unreasonable for a developer to supply a key greater than 512 bits,
// so there's no real harm in us limiting our PRF entropy to 512 bits (HMACSHA512).
//
// On 64-bit platforms, HMACSHA512 matches or outperforms HMACSHA256 in our perf testing.
// On 32-bit platforms, HMACSHA512 is around 1/3 the speed of HMACSHA256. In both cases, we
// try to cache the derived CryptographicKey wherever we can, so this shouldn't be a
// bottleneck regardless.
using (HMACSHA512 hmac = CryptoAlgorithms.CreateHMACSHA512(keyDerivationKey.GetKeyMaterial())) {
byte[] label, context;
purpose.GetKeyDerivationParameters(out label, out context);
byte[] derivedKey = DeriveKeyImpl(hmac, label, context, keyDerivationKey.KeyLength);
return new CryptographicKey(derivedKey);
}
}
// NOTE: This method also exists in Win8 (as BCryptKeyDerivation) and QTD (as DeriveKeySP800_108).
// However, the QTD implementation is currently incorrect, so we can't depend on it here. The below
// is a correct implementation. When we take a Win8 dependency, we can call into BCryptKeyDerivation.
private static byte[] DeriveKeyImpl(HMAC hmac, byte[] label, byte[] context, int keyLengthInBits) {
// This entire method is checked because according to SP800-108 it is an error
// for any single operation to result in overflow.
checked {
// Make a buffer which is ____ || label || 0x00 || context || [l]_2.
// We can reuse this buffer during each round.
int labelLength = (label != null) ? label.Length : 0;
int contextLength = (context != null) ? context.Length : 0;
byte[] buffer = new byte[4 /* [i]_2 */ + labelLength /* label */ + 1 /* 0x00 */ + contextLength /* context */ + 4 /* [L]_2 */];
if (labelLength != 0) {
Buffer.BlockCopy(label, 0, buffer, 4, labelLength); // the 4 accounts for the [i]_2 length
}
if (contextLength != 0) {
Buffer.BlockCopy(context, 0, buffer, 5 + labelLength, contextLength); // the '5 +' accounts for the [i]_2 length, the label, and the 0x00 byte
}
WriteUInt32ToByteArrayBigEndian((uint)keyLengthInBits, buffer, 5 + labelLength + contextLength); // the '5 +' accounts for the [i]_2 length, the label, the 0x00 byte, and the context
// Initialization
int numBytesWritten = 0;
int numBytesRemaining = keyLengthInBits / 8;
byte[] output = new byte[numBytesRemaining];
// Calculate each K_i value and copy the leftmost bits to the output buffer as appropriate.
for (uint i = 1; numBytesRemaining > 0; i++) {
WriteUInt32ToByteArrayBigEndian(i, buffer, 0); // set the first 32 bits of the buffer to be the current iteration value
byte[] K_i = hmac.ComputeHash(buffer);
// copy the leftmost bits of K_i into the output buffer
int numBytesToCopy = Math.Min(numBytesRemaining, K_i.Length);
Buffer.BlockCopy(K_i, 0, output, numBytesWritten, numBytesToCopy);
numBytesWritten += numBytesToCopy;
numBytesRemaining -= numBytesToCopy;
}
// finished
return output;
}
}
private static void WriteUInt32ToByteArrayBigEndian(uint value, byte[] buffer, int offset) {
buffer[offset + 0] = (byte)(value >> 24);
buffer[offset + 1] = (byte)(value >> 16);
buffer[offset + 2] = (byte)(value >> 8);
buffer[offset + 3] = (byte)(value);
}
}
}