e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
216 lines
12 KiB
C#
216 lines
12 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="MachineKey.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* MachineKey
|
|
*
|
|
* Copyright (c) 2009 Microsoft Corporation
|
|
*/
|
|
|
|
namespace System.Web.Security {
|
|
using System;
|
|
using System.Linq;
|
|
using System.Web.Configuration;
|
|
using System.Web.Security.Cryptography;
|
|
using System.Web.Util;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
public enum MachineKeyProtection {
|
|
All,
|
|
Encryption,
|
|
Validation
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
public static class MachineKey {
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
[Obsolete("This method is obsolete and is only provided for compatibility with existing code. It is recommended that new code use the Protect and Unprotect methods instead.")]
|
|
public static string Encode(byte[] data, MachineKeyProtection protectionOption) {
|
|
if (data == null)
|
|
throw new ArgumentNullException("data");
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Step 1: Get the MAC and add to the blob
|
|
if (protectionOption == MachineKeyProtection.All || protectionOption == MachineKeyProtection.Validation) {
|
|
byte[] bHash = MachineKeySection.HashData(data, null, 0, data.Length);
|
|
byte[] bAll = new byte[bHash.Length + data.Length];
|
|
Buffer.BlockCopy(data, 0, bAll, 0, data.Length);
|
|
Buffer.BlockCopy(bHash, 0, bAll, data.Length, bHash.Length);
|
|
data = bAll;
|
|
}
|
|
|
|
if (protectionOption == MachineKeyProtection.All || protectionOption == MachineKeyProtection.Encryption) {
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Step 2: Encryption
|
|
data = MachineKeySection.EncryptOrDecryptData(true, data, null, 0, data.Length, false, false, IVType.Random, !AppSettings.UseLegacyMachineKeyEncryption);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Step 3: Covert the buffer to HEX string and return it
|
|
return CryptoUtil.BinaryToHex(data);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
[Obsolete("This method is obsolete and is only provided for compatibility with existing code. It is recommended that new code use the Protect and Unprotect methods instead.")]
|
|
public static byte[] Decode(string encodedData, MachineKeyProtection protectionOption) {
|
|
if (encodedData == null)
|
|
throw new ArgumentNullException("encodedData");
|
|
|
|
if ((encodedData.Length % 2) != 0)
|
|
throw new ArgumentException(null, "encodedData");
|
|
|
|
byte[] data = null;
|
|
try {
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Step 1: Covert the HEX string to byte array
|
|
data = CryptoUtil.HexToBinary(encodedData);
|
|
}
|
|
catch {
|
|
throw new ArgumentException(null, "encodedData");
|
|
}
|
|
|
|
if (data == null || data.Length < 1)
|
|
throw new ArgumentException(null, "encodedData");
|
|
|
|
if (protectionOption == MachineKeyProtection.All || protectionOption == MachineKeyProtection.Encryption) {
|
|
//////////////////////////////////////////////////////////////////
|
|
// Step 2: Decrypt the data
|
|
data = MachineKeySection.EncryptOrDecryptData(false, data, null, 0, data.Length, false, false, IVType.Random, !AppSettings.UseLegacyMachineKeyEncryption);
|
|
if (data == null)
|
|
return null;
|
|
}
|
|
|
|
if (protectionOption == MachineKeyProtection.All || protectionOption == MachineKeyProtection.Validation) {
|
|
//////////////////////////////////////////////////////////////////
|
|
// Step 3a: Remove the hash from the end of the data
|
|
if (data.Length < MachineKeySection.HashSize)
|
|
return null;
|
|
byte[] originalData = data;
|
|
data = new byte[originalData.Length - MachineKeySection.HashSize];
|
|
Buffer.BlockCopy(originalData, 0, data, 0, data.Length);
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// Step 3b: Calculate the hash and make sure it matches
|
|
byte[] bHash = MachineKeySection.HashData(data, null, 0, data.Length);
|
|
if (bHash == null || bHash.Length != MachineKeySection.HashSize)
|
|
return null; // Sizes don't match
|
|
for (int iter = 0; iter < bHash.Length; iter++) {
|
|
if (bHash[iter] != originalData[data.Length + iter])
|
|
return null; // Mis-match found
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cryptographically protects and tamper-proofs the specified data.
|
|
/// </summary>
|
|
/// <param name="userData">The plaintext data that needs to be protected.</param>
|
|
/// <param name="purposes">(optional) A list of purposes that describe what the data is meant for.
|
|
/// If this value is specified, the same list must be passed to the Unprotect method in order
|
|
/// to decipher the returned ciphertext.</param>
|
|
/// <returns>The ciphertext data. To decipher the data, call the Unprotect method, passing this
|
|
/// value as the 'protectedData' parameter.</returns>
|
|
/// <remarks>
|
|
/// This method supercedes the Encode method, which required the caller to know whether he wanted
|
|
/// the plaintext data to be encrypted, signed, or both. In contrast, the Protect method just
|
|
/// does the right thing and securely protects the data. Ciphertext data produced by this method
|
|
/// can only be deciphered by the Unprotect method.
|
|
///
|
|
/// The 'purposes' parameter is an optional list of reason strings that can lock the ciphertext
|
|
/// to a specific purpose. The intent of this parameter is that different subsystems within
|
|
/// an application may depend on cryptographic operations, and a malicious client should not be
|
|
/// able to get the result of one subsystem's Protect method and feed it as input to another
|
|
/// subsystem's Unprotect method, which could have undesirable or insecure behavior. In essence,
|
|
/// the 'purposes' parameter helps ensure that some protected data can be consumed only by the
|
|
/// component that originally generated it. Applications should take care to ensure that each
|
|
/// subsystem uses a unique 'purposes' list.
|
|
///
|
|
/// For example, to protect or unprotect an authentication token, the application could call:
|
|
/// MachineKey.Protect(..., "Authentication token");
|
|
/// MachineKey.Unprotect(..., "Authentication token");
|
|
///
|
|
/// Applications may dynamically generate the 'purposes' parameter if desired. If an application
|
|
/// does this, user-supplied values like usernames should never directly be passed for the 'purposes'
|
|
/// parameter. They should instead be prefixed with something (like "Username: " + username) to
|
|
/// minimize the risk of a malicious client crafting input that collides with a token in use by some
|
|
/// other part of the system. Any dynamically-generated tokens should come after non-dynamically
|
|
/// generated tokens.
|
|
///
|
|
/// For example, to protect or unprotect a private message that is tied to a specific user, the
|
|
/// application could call:
|
|
/// MachineKey.Protect(..., "Private message", "Recipient: " + username);
|
|
/// MachineKey.Unprotect(..., "Private message", "Recipient: " + username);
|
|
///
|
|
/// In both of the above examples, is it important that the caller of the Unprotect method be able to
|
|
/// resurrect the original 'purposes' list. Otherwise the operation will fail with a CryptographicException.
|
|
/// </remarks>
|
|
public static byte[] Protect(byte[] userData, params string[] purposes) {
|
|
if (userData == null) {
|
|
throw new ArgumentNullException("userData");
|
|
}
|
|
|
|
// Technically we don't care if the purposes array contains whitespace-only entries,
|
|
// but the DataProtector class does, so we'll just block them right here.
|
|
if (purposes != null && purposes.Any(String.IsNullOrWhiteSpace)) {
|
|
throw new ArgumentException(SR.GetString(SR.MachineKey_InvalidPurpose), "purposes");
|
|
}
|
|
|
|
return Protect(AspNetCryptoServiceProvider.Instance, userData, purposes);
|
|
}
|
|
|
|
// Internal method for unit testing.
|
|
internal static byte[] Protect(ICryptoServiceProvider cryptoServiceProvider, byte[] userData, string[] purposes) {
|
|
// If the user is calling this method, we want to use the ICryptoServiceProvider
|
|
// regardless of whether or not it's the default provider.
|
|
|
|
Purpose derivedPurpose = Purpose.User_MachineKey_Protect.AppendSpecificPurposes(purposes);
|
|
ICryptoService cryptoService = cryptoServiceProvider.GetCryptoService(derivedPurpose);
|
|
return cryptoService.Protect(userData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies the integrity of and deciphers the given ciphertext.
|
|
/// </summary>
|
|
/// <param name="protectedData">Ciphertext data that was produced by the Protect method.</param>
|
|
/// <param name="purposes">(optional) A list of purposes that describe what the data is meant for.</param>
|
|
/// <returns>The plaintext data.</returns>
|
|
/// <exception>Throws a CryptographicException if decryption fails. This can occur if the 'protectedData' has
|
|
/// been tampered with, if an incorrect 'purposes' parameter is specified, or if an application is deployed
|
|
/// to more than one server (as in a farm scenario) but is using auto-generated encryption keys.</exception>
|
|
/// <remarks>See documentation on the Protect method for more information.</remarks>
|
|
public static byte[] Unprotect(byte[] protectedData, params string[] purposes) {
|
|
if (protectedData == null) {
|
|
throw new ArgumentNullException("protectedData");
|
|
}
|
|
|
|
// Technically we don't care if the purposes array contains whitespace-only entries,
|
|
// but the DataProtector class does, so we'll just block them right here.
|
|
if (purposes != null && purposes.Any(String.IsNullOrWhiteSpace)) {
|
|
throw new ArgumentException(SR.GetString(SR.MachineKey_InvalidPurpose), "purposes");
|
|
}
|
|
|
|
return Unprotect(AspNetCryptoServiceProvider.Instance, protectedData, purposes);
|
|
}
|
|
|
|
// Internal method for unit testing.
|
|
internal static byte[] Unprotect(ICryptoServiceProvider cryptoServiceProvider, byte[] protectedData, string[] purposes) {
|
|
// If the user is calling this method, we want to use the ICryptoServiceProvider
|
|
// regardless of whether or not it's the default provider.
|
|
|
|
Purpose derivedPurpose = Purpose.User_MachineKey_Protect.AppendSpecificPurposes(purposes);
|
|
ICryptoService cryptoService = cryptoServiceProvider.GetCryptoService(derivedPurpose);
|
|
return cryptoService.Unprotect(protectedData);
|
|
}
|
|
|
|
}
|
|
}
|