diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2013-02-25 21:26:04 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2013-02-25 21:26:04 -0800 |
commit | 38a1162c5cbaea035e655dc9accd92f9de5019ed (patch) | |
tree | 489ba7dfa106d5b0a8878ac386f2d2130bdf6b21 /src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs | |
parent | 10fc3ad3a7feda0cb5ab64aabe2e26bbce94595a (diff) | |
download | DotNetOpenAuth-38a1162c5cbaea035e655dc9accd92f9de5019ed.zip DotNetOpenAuth-38a1162c5cbaea035e655dc9accd92f9de5019ed.tar.gz DotNetOpenAuth-38a1162c5cbaea035e655dc9accd92f9de5019ed.tar.bz2 |
OAuth 1.0 Consumers are now *much* simpler, entirely avoiding channels.
Build breaks in other projects, however.
Diffstat (limited to 'src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs')
-rw-r--r-- | src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs | 354 |
1 files changed, 0 insertions, 354 deletions
diff --git a/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs b/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs deleted file mode 100644 index eb2020b..0000000 --- a/src/DotNetOpenAuth.AspNet/MachineKeyUtil.cs +++ /dev/null @@ -1,354 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="MachineKeyUtil.cs" company="Microsoft"> -// Copyright (c) Microsoft. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.AspNet { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Net; - using System.Reflection; - using System.Security.Cryptography; - using System.Text; - using System.Web; - using System.Web.Security; - - /// <summary> - /// Provides helpers that mimic the ASP.NET 4.5 MachineKey.Protect / Unprotect APIs, - /// even when running on ASP.NET 4.0. Consumers are expected to follow the same - /// conventions used by the MachineKey.Protect / Unprotect APIs (consult MSDN docs - /// for how these are meant to be used). Additionally, since this helper class - /// dynamically switches between the two based on whether the current application is - /// .NET 4.0 or 4.5, consumers should never persist output from the Protect method - /// since the implementation will change when upgrading 4.0 -> 4.5. This should be - /// used for transient data only. - /// </summary> - internal static class MachineKeyUtil { - /// <summary> - /// MachineKey implementation depending on the target .NET framework version - /// </summary> - private static readonly IMachineKey MachineKeyImpl = GetMachineKeyImpl(); - - /// <summary> - /// ProtectUnprotect delegate. - /// </summary> - /// <param name="data">The data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>Result of either Protect or Unprotect methods.</returns> - private delegate byte[] ProtectUnprotect(byte[] data, string[] purposes); - - /// <summary> - /// Abstract the MachineKey implementation in .NET 4.0 and 4.5 - /// </summary> - private interface IMachineKey { - /// <summary> - /// Protects the specified user data. - /// </summary> - /// <param name="userData">The user data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The protected data.</returns> - byte[] Protect(byte[] userData, string[] purposes); - - /// <summary> - /// Unprotects the specified protected data. - /// </summary> - /// <param name="protectedData">The protected data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The unprotected data.</returns> - byte[] Unprotect(byte[] protectedData, string[] purposes); - } - - /// <summary> - /// Protects the specified user data. - /// </summary> - /// <param name="userData">The user data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The encrypted data</returns> - public static byte[] Protect(byte[] userData, params string[] purposes) { - return MachineKeyImpl.Protect(userData, purposes); - } - - /// <summary> - /// Unprotects the specified protected data. - /// </summary> - /// <param name="protectedData">The protected data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The unencrypted data</returns> - public static byte[] Unprotect(byte[] protectedData, params string[] purposes) { - return MachineKeyImpl.Unprotect(protectedData, purposes); - } - - /// <summary> - /// Gets the machine key implementation based on the runtime framework version. - /// </summary> - /// <returns>The machine key implementation</returns> - private static IMachineKey GetMachineKeyImpl() { - // Late bind to the MachineKey.Protect / Unprotect methods only if <httpRuntime targetFramework="4.5" />. - // This helps ensure that round-tripping the payloads continues to work even if the application is - // deployed to a mixed 4.0 / 4.5 farm environment. - PropertyInfo targetFrameworkProperty = typeof(HttpRuntime).GetProperty("TargetFramework", typeof(Version)); - Version targetFramework = (targetFrameworkProperty != null) ? targetFrameworkProperty.GetValue(null, null) as Version : null; - if (targetFramework != null && targetFramework >= new Version(4, 5)) { - ProtectUnprotect protectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Protect", ignoreCase: false, throwOnBindFailure: false); - ProtectUnprotect unprotectThunk = (ProtectUnprotect)Delegate.CreateDelegate(typeof(ProtectUnprotect), typeof(MachineKey), "Unprotect", ignoreCase: false, throwOnBindFailure: false); - if (protectThunk != null && unprotectThunk != null) { - return new MachineKey45(protectThunk, unprotectThunk); // ASP.NET 4.5 - } - } - - return new MachineKey40(); // ASP.NET 4.0 - } - - /// <summary> - /// On ASP.NET 4.0, we perform some transforms which mimic the behaviors of MachineKey.Protect - /// and Unprotect. - /// </summary> - private sealed class MachineKey40 : IMachineKey { - /// <summary> - /// This is the magic header that identifies a MachineKey40 payload. - /// It helps differentiate this from other encrypted payloads.</summary> - private const uint MagicHeader = 0x8519140c; - - /// <summary> - /// The SHA-256 factory to be used. - /// </summary> - private static readonly Func<SHA256> sha256Factory = GetSHA256Factory(); - - /// <summary> - /// Protects the specified user data. - /// </summary> - /// <param name="userData">The user data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The protected data</returns> - public byte[] Protect(byte[] userData, string[] purposes) { - if (userData == null) { - throw new ArgumentNullException("userData"); - } - - // dataWithHeader = {magic header} .. {purposes} .. {userData} - byte[] dataWithHeader = new byte[checked(4 /* magic header */ + (256 / 8) /* purposes */ + userData.Length)]; - unchecked { - dataWithHeader[0] = (byte)(MagicHeader >> 24); - dataWithHeader[1] = (byte)(MagicHeader >> 16); - dataWithHeader[2] = (byte)(MagicHeader >> 8); - dataWithHeader[3] = (byte)MagicHeader; - } - byte[] purposeHash = ComputeSHA256(purposes); - Buffer.BlockCopy(purposeHash, 0, dataWithHeader, 4, purposeHash.Length); - Buffer.BlockCopy(userData, 0, dataWithHeader, 4 + (256 / 8), userData.Length); - - // encrypt + sign - string hexValue = MachineKey.Encode(dataWithHeader, MachineKeyProtection.All); - - // convert hex -> binary - byte[] binary = HexToBinary(hexValue); - return binary; - } - - /// <summary> - /// Unprotects the specified protected data. - /// </summary> - /// <param name="protectedData">The protected data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The unprotected data</returns> - public byte[] Unprotect(byte[] protectedData, string[] purposes) { - if (protectedData == null) { - throw new ArgumentNullException("protectedData"); - } - - // convert binary -> hex and calculate what the purpose should read - string hexEncodedData = BinaryToHex(protectedData); - byte[] purposeHash = ComputeSHA256(purposes); - - try { - // decrypt / verify signature - byte[] dataWithHeader = MachineKey.Decode(hexEncodedData, MachineKeyProtection.All); - - // validate magic header and purpose string - if (dataWithHeader != null - && dataWithHeader.Length >= (4 + (256 / 8)) - && (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(dataWithHeader, 0)) == MagicHeader - && AreByteArraysEqual(new ArraySegment<byte>(purposeHash), new ArraySegment<byte>(dataWithHeader, 4, 256 / 8))) { - // validation succeeded - byte[] userData = new byte[dataWithHeader.Length - 4 - (256 / 8)]; - Buffer.BlockCopy(dataWithHeader, 4 + (256 / 8), userData, 0, userData.Length); - return userData; - } - } - catch { - // swallow since will be rethrown immediately below - } - - // if we reached this point, some cryptographic operation failed - throw new CryptographicException(WebResources.Generic_CryptoFailure); - } - - /// <summary> - /// Convert bytes to hex string. - /// </summary> - /// <param name="binary">The input array.</param> - /// <returns>Hex string</returns> - internal static string BinaryToHex(byte[] binary) { - StringBuilder builder = new StringBuilder(checked(binary.Length * 2)); - for (int i = 0; i < binary.Length; i++) { - byte b = binary[i]; - builder.Append(HexDigit(b >> 4)); - builder.Append(HexDigit(b & 0x0F)); - } - string result = builder.ToString(); - return result; - } - - /// <summary> - /// This method is specially written to take the same amount of time - /// regardless of where 'a' and 'b' differ. Please do not optimize it.</summary> - /// <param name="a">first array.</param> - /// <param name="b">second array.</param> - /// <returns><c href="true" /> if equal, others <c href="false" /></returns> - private static bool AreByteArraysEqual(ArraySegment<byte> a, ArraySegment<byte> b) { - if (a.Count != b.Count) { - return false; - } - - bool areEqual = true; - for (int i = 0; i < a.Count; i++) { - areEqual &= a.Array[a.Offset + i] == b.Array[b.Offset + i]; - } - return areEqual; - } - - /// <summary> - /// Computes a SHA256 hash over all of the input parameters. - /// Each parameter is UTF8 encoded and preceded by a 7-bit encoded</summary> - /// integer describing the encoded byte length of the string. - /// <param name="parameters">The parameters.</param> - /// <returns>The output hash</returns> - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")] - private static byte[] ComputeSHA256(IList<string> parameters) { - using (MemoryStream ms = new MemoryStream()) { - using (BinaryWriter bw = new BinaryWriter(ms)) { - if (parameters != null) { - foreach (string parameter in parameters) { - bw.Write(parameter); // also writes the length as a prefix; unambiguous - } - bw.Flush(); - } - - using (SHA256 sha256 = sha256Factory()) { - byte[] retVal = sha256.ComputeHash(ms.GetBuffer(), 0, checked((int)ms.Length)); - return retVal; - } - } - } - } - - /// <summary> - /// Gets the SHA-256 factory. - /// </summary> - /// <returns>SHA256 factory</returns> - private static Func<SHA256> GetSHA256Factory() { - // Note: ASP.NET 4.5 always prefers CNG, but the CNG algorithms are not that - // performant on 4.0 and below. The following list is optimized for speed - // given our scenarios. - if (!CryptoConfig.AllowOnlyFipsAlgorithms) { - // This provider is not FIPS-compliant, so we can't use it if FIPS compliance - // is mandatory. - return () => new SHA256Managed(); - } - - try { - using (SHA256Cng sha256 = new SHA256Cng()) { - return () => new SHA256Cng(); - } - } - catch (PlatformNotSupportedException) { - // CNG not supported (perhaps because we're not on Windows Vista or above); move on - } - - // If all else fails, fall back to CAPI. - return () => new SHA256CryptoServiceProvider(); - } - - /// <summary> - /// Convert to hex character - /// </summary> - /// <param name="value">The value to be converted.</param> - /// <returns>Hex character</returns> - private static char HexDigit(int value) { - return (char)(value > 9 ? value + '7' : value + '0'); - } - - /// <summary> - /// Convert hdex string to bytes. - /// </summary> - /// <param name="hex">Input hex string.</param> - /// <returns>The bytes</returns> - private static byte[] HexToBinary(string hex) { - int size = hex.Length / 2; - byte[] bytes = new byte[size]; - for (int idx = 0; idx < size; idx++) { - bytes[idx] = (byte)((HexValue(hex[idx * 2]) << 4) + HexValue(hex[(idx * 2) + 1])); - } - return bytes; - } - - /// <summary> - /// Convert hex digit to byte. - /// </summary> - /// <param name="digit">The hex digit.</param> - /// <returns>The byte</returns> - private static int HexValue(char digit) { - return digit > '9' ? digit - '7' : digit - '0'; - } - } - - /// <summary> - /// On ASP.NET 4.5, we can just delegate to MachineKey.Protect and MachineKey.Unprotect directly, - /// which contain optimized code paths. - /// </summary> - private sealed class MachineKey45 : IMachineKey { - /// <summary> - /// Protect thunk - /// </summary> - private readonly ProtectUnprotect protectThunk; - - /// <summary> - /// Unprotect thunk - /// </summary> - private readonly ProtectUnprotect unprotectThunk; - - /// <summary> - /// Initializes a new instance of the <see cref="MachineKey45"/> class. - /// </summary> - /// <param name="protectThunk">The protect thunk.</param> - /// <param name="unprotectThunk">The unprotect thunk.</param> - public MachineKey45(ProtectUnprotect protectThunk, ProtectUnprotect unprotectThunk) { - this.protectThunk = protectThunk; - this.unprotectThunk = unprotectThunk; - } - - /// <summary> - /// Protects the specified user data. - /// </summary> - /// <param name="userData">The user data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The protected data</returns> - public byte[] Protect(byte[] userData, string[] purposes) { - return this.protectThunk(userData, purposes); - } - - /// <summary> - /// Unprotects the specified protected data. - /// </summary> - /// <param name="protectedData">The protected data.</param> - /// <param name="purposes">The purposes.</param> - /// <returns>The unprotected data</returns> - public byte[] Unprotect(byte[] protectedData, string[] purposes) { - return this.unprotectThunk(protectedData, purposes); - } - } - } -} |