//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ /* * 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; } /// /// Cryptographically protects and tamper-proofs the specified data. /// /// The plaintext data that needs to be protected. /// (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. /// The ciphertext data. To decipher the data, call the Unprotect method, passing this /// value as the 'protectedData' parameter. /// /// 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. /// 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); } /// /// Verifies the integrity of and deciphers the given ciphertext. /// /// Ciphertext data that was produced by the Protect method. /// (optional) A list of purposes that describe what the data is meant for. /// The plaintext data. /// 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. /// See documentation on the Protect method for more information. 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); } } }