1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
//------------------------------------------------------------------------------
// <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);
}
}
}
|