File: Security\MachineKey.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <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);
        }
 
    }
}