├── AspNetCrypter ├── App.config ├── System.Web.Security.Cryptography │ ├── IMasterKeyProvider.cs │ ├── KeyDerivationFunction.cs │ ├── ICryptoServiceProvider.cs │ ├── CryptoServiceOptions.cs │ ├── ICryptoAlgorithmFactory.cs │ ├── ICryptoService.cs │ ├── CryptographicKey.cs │ ├── HomogenizingCryptoServiceWrapper.cs │ ├── CryptoAlgorithms.cs │ ├── SP800_108.cs │ ├── CryptoUtil.cs │ ├── Purpose.cs │ └── NetFXCryptoService.cs ├── Properties │ └── AssemblyInfo.cs ├── System.Web.Util │ ├── HttpEncoderUtility.cs │ ├── Utf16StringValidator.cs │ └── HttpEncoder.cs ├── AspNetDecryptor.cs ├── AspNetCrypter.csproj ├── Hex │ ├── Hex.cs │ └── HexEncoder.cs ├── Program.cs └── Options.cs ├── AspNetDerive ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── Program.cs └── AspNetDerive.csproj ├── LICENSE ├── AspNetCrypter.sln ├── .gitattributes ├── .gitignore └── README.md /AspNetCrypter/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AspNetDerive/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/IMasterKeyProvider.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | 10 | // Represents an object that can provide master encryption / validation keys 11 | 12 | internal interface IMasterKeyProvider { 13 | 14 | // encryption + decryption key 15 | CryptographicKey GetEncryptionKey(); 16 | 17 | // signing + validation key 18 | CryptographicKey GetValidationKey(); 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/KeyDerivationFunction.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | 10 | // A delegate that represents a cryptographic key derivation function (KDF). 11 | // A KDF takes a master key (the key derivation key) and a purpose string, 12 | // producing a derived key in the process. 13 | internal delegate CryptographicKey KeyDerivationFunction(CryptographicKey keyDerivationKey, Purpose purpose); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/ICryptoServiceProvider.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | 10 | // Represents an object that can provide ICryptoService instances. 11 | // Get an instance of this type via the AspNetCryptoServiceProvider.Instance singleton property. 12 | 13 | internal interface ICryptoServiceProvider { 14 | 15 | ICryptoService GetCryptoService(Purpose purpose, CryptoServiceOptions options = CryptoServiceOptions.None); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/CryptoServiceOptions.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | 10 | // Describes options that can configure an ICryptoService. 11 | 12 | internal enum CryptoServiceOptions { 13 | 14 | // [default] no special behavior needed 15 | None = 0, 16 | 17 | // the output of the Protect method will be cached, so the same plaintext should lead to the same ciphertext (no randomness) 18 | CacheableOutput, 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/ICryptoAlgorithmFactory.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | using System.Security.Cryptography; 10 | 11 | // Represents an object that can provide encryption + validation algorithm instances 12 | 13 | internal interface ICryptoAlgorithmFactory { 14 | 15 | // Gets a SymmetricAlgorithm instance that can be used for encryption / decryption 16 | SymmetricAlgorithm GetEncryptionAlgorithm(); 17 | 18 | // Gets a KeyedHashAlgorithm instance that can be used for signing / validation 19 | KeyedHashAlgorithm GetValidationAlgorithm(); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/ICryptoService.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | 10 | // Represents an object that can perform cryptographic operations. 11 | // Get an instance of this class via an ICryptoServiceProvider (like AspNetCryptoServiceProvider). 12 | 13 | internal interface ICryptoService { 14 | 15 | // Protects some data by applying appropriate cryptographic transformations to it. 16 | byte[] Protect(byte[] clearData); 17 | 18 | // Returns the unprotected form of some protected data by validating and undoing the cryptographic transformations that led to it. 19 | byte[] Unprotect(byte[] protectedData); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2016 Sebastian Solnica 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /AspNetCrypter.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCrypter", "AspNetCrypter\AspNetCrypter.csproj", "{9B71A758-F3B5-4C19-BA7A-9546DA73F60B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetDerive", "AspNetDerive\AspNetDerive.csproj", "{7BEFDB83-2FD1-4C5C-9818-B96948F98378}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {9B71A758-F3B5-4C19-BA7A-9546DA73F60B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {9B71A758-F3B5-4C19-BA7A-9546DA73F60B}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {9B71A758-F3B5-4C19-BA7A-9546DA73F60B}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {9B71A758-F3B5-4C19-BA7A-9546DA73F60B}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {7BEFDB83-2FD1-4C5C-9818-B96948F98378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {7BEFDB83-2FD1-4C5C-9818-B96948F98378}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {7BEFDB83-2FD1-4C5C-9818-B96948F98378}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {7BEFDB83-2FD1-4C5C-9818-B96948F98378}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /AspNetCrypter/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AspNetCrypter")] 9 | [assembly: AssemblyDescription("AspNetCrypter - a tool for decrypting ASP.NET protected data")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("LowLevelDesign")] 12 | [assembly: AssemblyProduct("AspNetCrypter")] 13 | [assembly: AssemblyCopyright("Copyright © 2016 Sebastian Solnica (@lowleveldesign)")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9b71a758-f3b5-4c19-ba7a-9546da73f60b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.0.2.0")] 36 | [assembly: AssemblyFileVersion("0.0.2.0")] 37 | -------------------------------------------------------------------------------- /AspNetDerive/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AspNetDerive")] 9 | [assembly: AssemblyDescription("AspNetDerive - a tool to create the derivative ASP.NET keys")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("LowLevelDesign")] 12 | [assembly: AssemblyProduct("AspNetDerive")] 13 | [assembly: AssemblyCopyright("Copyright © 2016 Sebastian Solnica (@lowleveldesign)")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7befdb83-2fd1-4c5c-9818-b96948f98378")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Util/HttpEncoderUtility.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | /* 8 | * Helper class for common encoding routines 9 | * 10 | * Copyright (c) 2009 Microsoft Corporation 11 | */ 12 | 13 | namespace System.Web.Util { 14 | using System; 15 | using System.Diagnostics; 16 | 17 | internal static class HttpEncoderUtility { 18 | 19 | public static int HexToInt(char h) { 20 | return (h >= '0' && h <= '9') ? h - '0' : 21 | (h >= 'a' && h <= 'f') ? h - 'a' + 10 : 22 | (h >= 'A' && h <= 'F') ? h - 'A' + 10 : 23 | -1; 24 | } 25 | 26 | public static char IntToHex(int n) { 27 | Debug.Assert(n < 0x10); 28 | 29 | if (n <= 9) 30 | return (char)(n + (int)'0'); 31 | else 32 | return (char)(n - 10 + (int)'a'); 33 | } 34 | 35 | // Set of safe chars, from RFC 1738.4 minus '+' 36 | public static bool IsUrlSafeChar(char ch) { 37 | if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) 38 | return true; 39 | 40 | switch (ch) { 41 | case '-': 42 | case '_': 43 | case '.': 44 | case '!': 45 | case '*': 46 | case '(': 47 | case ')': 48 | return true; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | // Helper to encode spaces only 55 | internal static String UrlEncodeSpaces(string str) { 56 | if (str != null && str.IndexOf(' ') >= 0) 57 | str = str.Replace(" ", "%20"); 58 | return str; 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/CryptographicKey.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using Diagnostics; 9 | using System; 10 | 11 | // Represents a key that can be used for a cryptographic operation. 12 | 13 | internal sealed class CryptographicKey { 14 | 15 | private readonly byte[] _keyMaterial; 16 | 17 | public CryptographicKey(byte[] keyMaterial) { 18 | _keyMaterial = keyMaterial; 19 | } 20 | 21 | // Returns the length of the key (in bits). 22 | public int KeyLength { 23 | get { 24 | return checked(_keyMaterial.Length * 8); 25 | } 26 | } 27 | 28 | // Extracts the specified number of bits at the specified offset 29 | // and returns a new CryptographicKey. This is not appropriate 30 | // for subkey derivation, but it can be used if this cryptographic 31 | // key is actually two keys (like encryption + validation) 32 | // concatenated together. Inputs are specified as bit lengths. 33 | public CryptographicKey ExtractBits(int offset, int count) { 34 | Debug.Assert(offset % 8 == 0, "Offset must be divisible by 8."); 35 | Debug.Assert(count % 8 == 0, "Count must be divisible by 8."); 36 | 37 | int offsetBytes = offset / 8; 38 | int countBytes = count / 8; 39 | 40 | byte[] newKey = new byte[countBytes]; 41 | Buffer.BlockCopy(_keyMaterial, offsetBytes, newKey, 0, countBytes); 42 | return new CryptographicKey(newKey); 43 | } 44 | 45 | // Returns the raw key material as a byte array. 46 | public byte[] GetKeyMaterial() { 47 | return _keyMaterial; 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/HomogenizingCryptoServiceWrapper.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | using System.Configuration; 10 | using System.Security.Cryptography; 11 | 12 | // Wraps an ICryptoService instance and homogenizes any exceptions that might occur. 13 | 14 | internal sealed class HomogenizingCryptoServiceWrapper : ICryptoService { 15 | 16 | public HomogenizingCryptoServiceWrapper(ICryptoService wrapped) { 17 | WrappedCryptoService = wrapped; 18 | } 19 | 20 | internal ICryptoService WrappedCryptoService { 21 | get; 22 | private set; 23 | } 24 | 25 | private static byte[] HomogenizeErrors(Func func, byte[] input) { 26 | // If the underlying method returns null or throws an exception, the 27 | // error will be homogenized as a single CryptographicException. 28 | 29 | byte[] output = null; 30 | bool allowExceptionToBubble = false; 31 | 32 | try { 33 | output = func(input); 34 | return output; 35 | } 36 | catch (ConfigurationException) { 37 | // ConfigurationException isn't a side channel; it means the application is misconfigured. 38 | // We need to bubble this up so that the developer can react to it. 39 | allowExceptionToBubble = true; 40 | throw; 41 | } 42 | finally { 43 | if (output == null && !allowExceptionToBubble) { 44 | throw new CryptographicException(); 45 | } 46 | } 47 | } 48 | 49 | public byte[] Protect(byte[] clearData) { 50 | return HomogenizeErrors(WrappedCryptoService.Protect, clearData); 51 | } 52 | 53 | public byte[] Unprotect(byte[] protectedData) { 54 | return HomogenizeErrors(WrappedCryptoService.Unprotect, protectedData); 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Util/Utf16StringValidator.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Util { 8 | using System; 9 | 10 | // This class contains utility methods for dealing with security contexts when crossing AppDomain boundaries. 11 | 12 | internal static class Utf16StringValidator { 13 | 14 | private const char UNICODE_NULL_CHAR = '\0'; 15 | private const char UNICODE_REPLACEMENT_CHAR = '\uFFFD'; 16 | 17 | private static readonly bool _skipUtf16Validation = false; // CHANGED: AppSettings.AllowRelaxedUnicodeDecoding; 18 | 19 | public static string ValidateString(string input) { 20 | return ValidateString(input, _skipUtf16Validation); 21 | } 22 | 23 | // only internal for unit testing 24 | internal static string ValidateString(string input, bool skipUtf16Validation) { 25 | if (skipUtf16Validation || String.IsNullOrEmpty(input)) { 26 | return input; 27 | } 28 | 29 | // locate the first surrogate character 30 | int idxOfFirstSurrogate = -1; 31 | for (int i = 0; i < input.Length; i++) { 32 | if (Char.IsSurrogate(input[i])) { 33 | idxOfFirstSurrogate = i; 34 | break; 35 | } 36 | } 37 | 38 | // fast case: no surrogates = return input string 39 | if (idxOfFirstSurrogate < 0) { 40 | return input; 41 | } 42 | 43 | // slow case: surrogates exist, so we need to validate them 44 | char[] chars = input.ToCharArray(); 45 | for (int i = idxOfFirstSurrogate; i < chars.Length; i++) { 46 | char thisChar = chars[i]; 47 | 48 | // If this character is a low surrogate, then it was not preceded by 49 | // a high surrogate, so we'll replace it. 50 | if (Char.IsLowSurrogate(thisChar)) { 51 | chars[i] = UNICODE_REPLACEMENT_CHAR; 52 | continue; 53 | } 54 | 55 | if (Char.IsHighSurrogate(thisChar)) { 56 | // If this character is a high surrogate and it is followed by a 57 | // low surrogate, allow both to remain. 58 | if (i + 1 < chars.Length && Char.IsLowSurrogate(chars[i + 1])) { 59 | i++; // skip the low surrogate also 60 | continue; 61 | } 62 | 63 | // If this character is a high surrogate and it is not followed 64 | // by a low surrogate, replace it. 65 | chars[i] = UNICODE_REPLACEMENT_CHAR; 66 | continue; 67 | } 68 | 69 | // Otherwise, this is a non-surrogate character and just move to the 70 | // next character. 71 | } 72 | return new String(chars); 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AspNetDerive/Program.cs: -------------------------------------------------------------------------------- 1 | using NDesk.Options; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Web.Security.Cryptography; 10 | 11 | namespace LowLevelDesign.AspNetDerive 12 | { 13 | class Program 14 | { 15 | static void Main(string[] args) 16 | { 17 | string key = null, context = null, label = null; 18 | string[] labels = new string[0]; 19 | bool showhelp = false; 20 | 21 | var p = new OptionSet 22 | { 23 | { "k|key=", "the validation key (in hex)", v => key = v }, 24 | { "c|context=", "the context", v => context = v }, 25 | { "l|labels=", "the labels, separated by commas", v => label = v }, 26 | { "h|help", "show this message and exit", v => showhelp = v != null }, 27 | { "?", "show this message and exit", v => showhelp = v != null } 28 | }; 29 | 30 | try { 31 | p.Parse(args).FirstOrDefault(); 32 | } catch (OptionException ex) { 33 | Console.Error.Write("ERROR: invalid argument, "); 34 | Console.Error.WriteLine(ex.Message); 35 | Console.Error.WriteLine(); 36 | showhelp = true; 37 | } 38 | if (!showhelp && key == null) { 39 | Console.Error.WriteLine("ERROR: the key is missing"); 40 | Console.Error.WriteLine(); 41 | showhelp = true; 42 | } 43 | if (label != null) { 44 | labels = label.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 45 | } 46 | if (!showhelp && context == null) { 47 | Console.Error.WriteLine("ERROR: the context is missing"); 48 | Console.Error.WriteLine(); 49 | showhelp = true; 50 | } 51 | if (showhelp) { 52 | ShowHelp(p); 53 | return; 54 | } 55 | 56 | Debug.Assert(context != null); 57 | Debug.Assert(key != null); 58 | 59 | 60 | if (key.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { 61 | key = key.Substring(2); 62 | } 63 | 64 | var purpose = new Purpose(context, labels); 65 | var keyBytes = CryptoUtil.HexToBinary(key); 66 | if (keyBytes == null) { 67 | Console.Error.WriteLine("ERROR: the key is invalid"); 68 | Console.Error.WriteLine(); 69 | return; 70 | } 71 | 72 | Console.WriteLine(Hexify.Hex.PrettyPrint(SP800_108.DeriveKey( 73 | new CryptographicKey(keyBytes), purpose).GetKeyMaterial())); 74 | } 75 | 76 | static void ShowHelp(OptionSet p) 77 | { 78 | Console.WriteLine("{0} v{1} - {2}", Assembly.GetExecutingAssembly().GetCustomAttribute().Title, 79 | Assembly.GetExecutingAssembly().GetName().Version.ToString(), 80 | Assembly.GetExecutingAssembly().GetCustomAttribute().Description); 81 | Console.WriteLine(Assembly.GetExecutingAssembly().GetCustomAttribute().Copyright); 82 | Console.WriteLine(); 83 | Console.WriteLine("Usage: aspnetderive [OPTIONS]"); 84 | Console.WriteLine(); 85 | Console.WriteLine("Options:"); 86 | p.WriteOptionDescriptions(Console.Out); 87 | Console.WriteLine(); 88 | 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | !ExternalLibs/*.* 4 | !Tools/nuget.exe 5 | 6 | build-nuget-package.bat 7 | *.nupkg 8 | 9 | # User-specific files 10 | *.suo 11 | *.user 12 | *.sln.docstates 13 | .vs 14 | 15 | LowLevelDesign.Diagnostics.Castle/Content/updates/* 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | x64/ 22 | build/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | 27 | # Roslyn cache directories 28 | *.ide/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | #NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | *_i.c 44 | *_p.c 45 | *_i.h 46 | *.ilk 47 | *.meta 48 | *.obj 49 | *.pch 50 | *.pdb 51 | *.pgc 52 | *.pgd 53 | *.rsp 54 | *.sbr 55 | *.tlb 56 | *.tli 57 | *.tlh 58 | *.tmp 59 | *.tmp_proj 60 | *.log 61 | *.vspscc 62 | *.vssscc 63 | .builds 64 | *.pidb 65 | *.svclog 66 | *.scc 67 | 68 | # Chutzpah Test files 69 | _Chutzpah* 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opensdf 76 | *.sdf 77 | *.cachefile 78 | 79 | # Visual Studio profiler 80 | *.psess 81 | *.vsp 82 | *.vspx 83 | 84 | # TFS 2012 Local Workspace 85 | $tf/ 86 | 87 | # Guidance Automation Toolkit 88 | *.gpState 89 | 90 | # ReSharper is a .NET coding add-in 91 | _ReSharper*/ 92 | *.[Rr]e[Ss]harper 93 | *.DotSettings.user 94 | 95 | # JustCode is a .NET coding addin-in 96 | .JustCode 97 | 98 | # TeamCity is a build add-in 99 | _TeamCity* 100 | 101 | # DotCover is a Code Coverage Tool 102 | *.dotCover 103 | 104 | # NCrunch 105 | _NCrunch_* 106 | .*crunch*.local.xml 107 | 108 | # MightyMoose 109 | *.mm.* 110 | AutoTest.Net/ 111 | 112 | # Web workbench (sass) 113 | .sass-cache/ 114 | 115 | # Installshield output folder 116 | [Ee]xpress/ 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish/ 130 | 131 | # Publish Web Output 132 | *.[Pp]ublish.xml 133 | *.azurePubxml 134 | ## TODO: Comment the next line if you want to checkin your 135 | ## web deploy settings but do note that will include unencrypted 136 | ## passwords 137 | #*.pubxml 138 | 139 | # NuGet Packages Directory 140 | packages/* 141 | ## TODO: If the tool you use requires repositories.config 142 | ## uncomment the next line 143 | #!packages/repositories.config 144 | 145 | # Enable "build/" folder in the NuGet Packages folder since 146 | # NuGet packages use it for MSBuild targets. 147 | # This line needs to be after the ignore of the build folder 148 | # (and the packages folder if the line above has been uncommented) 149 | !packages/build/ 150 | 151 | # Windows Azure Build Output 152 | csx/ 153 | *.build.csdef 154 | 155 | # Windows Store app package directory 156 | AppPackages/ 157 | 158 | # Others 159 | sql/ 160 | *.Cache 161 | ClientBin/ 162 | [Ss]tyle[Cc]op.* 163 | ~$* 164 | *~ 165 | *.dbmdl 166 | *.dbproj.schemaview 167 | *.pfx 168 | *.snk 169 | *.publishsettings 170 | node_modules/ 171 | 172 | # RIA/Silverlight projects 173 | Generated_Code/ 174 | 175 | # Backup & report files from converting an old project file 176 | # to a newer Visual Studio version. Backup files are not needed, 177 | # because we have git ;-) 178 | _UpgradeReport_Files/ 179 | Backup*/ 180 | UpgradeLog*.XML 181 | UpgradeLog*.htm 182 | 183 | # SQL Server files 184 | *.mdf 185 | *.ldf 186 | 187 | # Business Intelligence projects 188 | *.rdl.data 189 | *.bim.layout 190 | *.bim_*.settings 191 | 192 | # Microsoft Fakes 193 | FakesAssemblies/ 194 | 195 | # LightSwitch generated files 196 | GeneratedArtifacts/ 197 | _Pvt_Extensions/ 198 | ModelManifest.xml 199 | -------------------------------------------------------------------------------- /AspNetCrypter/AspNetDecryptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Security.Cryptography; 6 | using System.Web.Security.Cryptography; 7 | 8 | namespace LowLevelDesign.AspNetCrypter 9 | { 10 | internal sealed class AspNetDecryptor 11 | { 12 | private readonly CryptographicKey decryptionKey; 13 | private readonly CryptographicKey validationKey; 14 | private readonly bool isGzipped = false; 15 | 16 | public AspNetDecryptor(Purpose purpose, CryptographicKey decryptionKey, CryptographicKey validationKey, bool isGzipped) 17 | { 18 | this.decryptionKey = SP800_108.DeriveKey(decryptionKey, purpose); 19 | this.validationKey = SP800_108.DeriveKey(validationKey, purpose); 20 | this.isGzipped = isGzipped; 21 | } 22 | 23 | public byte[] DecryptData(byte[] data) 24 | { 25 | var cryptoService = new NetFXCryptoService(new GuessCryptoAlgorithmFactory(decryptionKey.KeyLength, 26 | validationKey.KeyLength), decryptionKey, validationKey); 27 | 28 | var decryptedData = cryptoService.Unprotect(data); 29 | return isGzipped ? Decompress(decryptedData) : decryptedData; 30 | } 31 | 32 | 33 | private byte[] Decompress(byte[] data) 34 | { 35 | using (var ms = new MemoryStream(data)) 36 | { 37 | using (MemoryStream decomp = new MemoryStream()) 38 | { 39 | using (GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress)) 40 | { 41 | gzip.CopyTo(decomp); 42 | } 43 | return decomp.ToArray(); 44 | } 45 | } 46 | } 47 | 48 | private class GuessCryptoAlgorithmFactory : ICryptoAlgorithmFactory 49 | { 50 | private readonly SymmetricAlgorithm decryptionAlgorithm; 51 | private readonly KeyedHashAlgorithm validationAlgorigthm; 52 | 53 | public GuessCryptoAlgorithmFactory(int symmetricKeyLength, int validationKeyLength) 54 | { 55 | switch (symmetricKeyLength) { 56 | case 64: 57 | decryptionAlgorithm = DES.Create(); 58 | break; 59 | case 192: 60 | decryptionAlgorithm = TripleDES.Create(); 61 | break; 62 | case 128: 63 | case 256: 64 | decryptionAlgorithm = Aes.Create(); 65 | break; 66 | default: 67 | throw new ArgumentException("Encryption algorithm could not be recognized."); 68 | } 69 | 70 | switch (validationKeyLength) { 71 | case 128: 72 | validationAlgorigthm = HMAC.Create("HMACMD5"); 73 | break; 74 | case 160: 75 | validationAlgorigthm = HMAC.Create("HMACSHA1"); 76 | break; 77 | case 256: 78 | validationAlgorigthm = HMAC.Create("HMACSHA256"); 79 | break; 80 | case 384: 81 | validationAlgorigthm = HMAC.Create("HMACSHA384"); 82 | break; 83 | case 512: 84 | validationAlgorigthm = HMAC.Create("HMACSHA512"); 85 | break; 86 | default: 87 | throw new ArgumentException("Validation algorithm could not be recognized."); 88 | } 89 | } 90 | 91 | public SymmetricAlgorithm GetEncryptionAlgorithm() 92 | { 93 | return decryptionAlgorithm; 94 | } 95 | 96 | public KeyedHashAlgorithm GetValidationAlgorithm() 97 | { 98 | return validationAlgorigthm; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /AspNetCrypter/AspNetCrypter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9B71A758-F3B5-4C19-BA7A-9546DA73F60B} 8 | Exe 9 | Properties 10 | LowLevelDesign.AspNetCrypter 11 | AspNetCrypter 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 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 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ASP.NET Crypter (AspNetCrypter) 3 | ------------------------------- 4 | 5 | It's a small tool to decrypt the ASP.NET protected data offline. The crypto code is copied from the Microsoft's [reference source repository](https://github.com/Microsoft/referencesource). For now it supports only owin.cookies, but please create an issue if you would like to have another part of ASP.NET decrypted. The command line looks as follows: 6 | 7 | ``` 8 | AspNetCrypter v0.0.1.0 - a tool for decryption ASP.NET protected data 9 | Copyright (C) 2016 Sebastian Solnica (@lowleveldesign) 10 | 11 | Usage: aspnetcrypter [OPTIONS] encrypteddata 12 | 13 | Options: 14 | --vk=VALUE the validation key (in hex) 15 | --dk=VALUE the decryption key (in hex) 16 | -p, --purpose=VALUE the encryption context 17 | (currently only: owin.cookie) 18 | --base64 data is provided in base64 format (otherwise we 19 | assume hex) 20 | -h, --help Show this message and exit 21 | -? Show this message and exit 22 | ``` 23 | 24 | Notice, you need to provide the master keys for encryption and validation. An example call might look as follows: 25 | 26 | ``` 27 | aspnetcrypter --dk=0xa5e27...281146d52 --vk=0x507de...34e29a820f6 --purpose=owin.cookie --base64 i1movjk3P...H0FEiSSYxvAy0HY6bIGJNbQ 28 | 29 | 0000: 03 00 00 00 11 41 70 70 6c 69 63 61 74 69 6f 6e .....Application 30 | 0010: 43 6f 6f 6b 69 65 01 00 01 00 04 00 00 00 44 68 Cookie........Dh 31 | 0020: 74 74 70 3a 2f 2f 73 63 68 65 6d 61 73 2e 78 6d ttp://schemas.xm 32 | 0030: 6c 73 6f 61 70 2e 6f 72 67 2f 77 73 2f 32 30 30 lsoap.org/ws/200 33 | 0040: 35 2f 30 35 2f 69 64 65 6e 74 69 74 79 2f 63 6c 5/05/identity/cl 34 | 0050: 61 69 6d 73 2f 6e 61 6d 65 69 64 65 6e 74 69 66 aims/nameidentif 35 | 0060: 69 65 72 24 31 64 35 31 62 32 34 63 2d 66 35 65 ier.1d51b24c-f5e 36 | 0070: 61 2d 34 61 33 62 2d 39 39 39 65 2d 63 35 37 31 a-4a3b-999e-c571 37 | 0080: 61 39 34 31 30 63 63 64 01 00 01 00 01 00 01 00 a9410ccd........ 38 | 0090: 0d 74 65 73 74 40 74 65 73 74 2e 63 6f 6d 01 00 .test@test.com.. 39 | 00a0: 01 00 01 00 51 68 74 74 70 3a 2f 2f 73 63 68 65 ....Qhttp://sche 40 | 00b0: 6d 61 73 2e 6d 69 63 72 6f 73 6f 66 74 2e 63 6f mas.microsoft.co 41 | 00c0: 6d 2f 61 63 63 65 73 73 63 6f 6e 74 72 6f 6c 73 m/accesscontrols 42 | 00d0: 65 72 76 69 63 65 2f 32 30 31 30 2f 30 37 2f 63 ervice/2010/07/c 43 | 00e0: 6c 61 69 6d 73 2f 69 64 65 6e 74 69 74 79 70 72 laims/identitypr 44 | 00f0: 6f 76 69 64 65 72 10 41 53 50 2e 4e 45 54 20 49 ovider.ASP.NET.I 45 | 0100: 64 65 6e 74 69 74 79 01 00 01 00 01 00 1d 41 73 dentity.......As 46 | 0110: 70 4e 65 74 2e 49 64 65 6e 74 69 74 79 2e 53 65 pNet.Identity.Se 47 | 0120: 63 75 72 69 74 79 53 74 61 6d 70 24 36 33 62 61 curityStamp.63ba 48 | 0130: 39 65 62 33 2d 33 66 64 38 2d 34 31 36 35 2d 39 9eb3-3fd8-4165-9 49 | 0140: 32 34 33 2d 38 37 33 62 64 33 66 62 64 34 35 39 243-873bd3fbd459 50 | 0150: 01 00 01 00 01 00 00 00 00 00 01 00 00 00 02 00 ................ 51 | 0160: 00 00 08 2e 65 78 70 69 72 65 73 1d 54 75 65 2c ....expires.Tue, 52 | 0170: 20 30 36 20 44 65 63 20 32 30 31 36 20 31 35 3a .06.Dec.2016.15: 53 | 0180: 30 34 3a 33 31 20 47 4d 54 07 2e 69 73 73 75 65 04:31.GMT..issue 54 | 0190: 64 1d 54 75 65 2c 20 32 32 20 4e 6f 76 20 32 30 d.Tue,.22.Nov.20 55 | 01a0: 31 36 20 31 35 3a 30 34 3a 33 31 20 47 4d 54 16.15:04:31.GMT 56 | ``` 57 | 58 | ASP.NET Key Derive Tool (AspNetDerive) 59 | ------------------------------------- 60 | 61 | A tool to calculate the derived ASP.NET keys, based on a master key. The command line looks as follows: 62 | 63 | ``` 64 | AspNetDerive v1.0.0.0 - AspNetDerive - a tool to create the derivative ASP.NET keys 65 | Copyright c 2016 Sebastian Solnica (@lowleveldesign) 66 | 67 | Usage: aspnetderive [OPTIONS] 68 | 69 | Options: 70 | -k, --key=VALUE the validation key (in hex) 71 | -c, --context=VALUE the context 72 | -l, --labels=VALUE the labels, separated by commas 73 | -h, --help show this message and exit 74 | -? show this message and exit 75 | ``` 76 | 77 | Example usage: 78 | 79 | ``` 80 | PS Debug> .\AspNetDerive.exe -k 1726E744C1FF4A6E84A1B511CDDADD10A1AB082044238A10533F8BBB87201926 -c "MachineKeyDerivation" -l "IsolateApps: /" 81 | 0000: f2 e0 94 2f 79 0a d1 bb 01 eb 90 50 5c 8b b8 c0 oa./y.N».ë.P\..A 82 | 0010: f5 28 41 9b bc fb 6a e2 42 cc cc 7b 51 52 53 8c o(A..ujâBII{QRS. 83 | ``` 84 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/CryptoAlgorithms.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | using System.Diagnostics.CodeAnalysis; 10 | using System.Security.Cryptography; 11 | 12 | // Utility class to provide the "one true way" of getting instances of 13 | // cryptographic algorithms, like SymmetricAlgorithm and HashAlgorithm. 14 | 15 | // From discussions with [....] and the crypto board, we should prefer 16 | // the CNG implementations of algorithms, then the CAPI implementations, 17 | // then finally managed implementations if there are no CNG / CAPI 18 | // implementations. The CNG / CAPI implementations are preferred for 19 | // expandability, FIPS-compliance, and performance. 20 | // 21 | // .NET Framework 4.5 allows us to make two core assumptions: 22 | // - The built-in HMAC classes have been updated for FIPS compliance. 23 | // - Since .NET 4.5 requires Windows Server 2008 or greater, we can 24 | // assume that CNG is available on the box. 25 | // 26 | // Note that some algorithms (MD5, DES, etc.) aren't FIPS-compliant 27 | // under any circumstance. Calling these methods when the OS is 28 | // configured to allow only FIPS-compliant algorithms will result 29 | // in an exception being thrown. 30 | // 31 | // The .NET Framework's built-in algorithms don't need to be created 32 | // under the application impersonation context since they don't depend 33 | // on the impersonated identity. 34 | 35 | internal static class CryptoAlgorithms { 36 | 37 | internal static Aes CreateAes() { 38 | return new AesCryptoServiceProvider(); 39 | } 40 | 41 | [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5351:DESCannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")] 42 | [Obsolete("DES is deprecated and MUST NOT be used by new features. Consider using AES instead.")] 43 | internal static DES CreateDES() { 44 | return new DESCryptoServiceProvider(); 45 | } 46 | 47 | [SuppressMessage("Microsoft.Security.Cryptography", "CA5354:SHA1CannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")] 48 | internal static HMACSHA1 CreateHMACSHA1() { 49 | return new HMACSHA1(); 50 | } 51 | 52 | internal static HMACSHA256 CreateHMACSHA256() { 53 | return new HMACSHA256(); 54 | } 55 | 56 | internal static HMACSHA384 CreateHMACSHA384() { 57 | return new HMACSHA384(); 58 | } 59 | 60 | internal static HMACSHA512 CreateHMACSHA512() { 61 | return new HMACSHA512(); 62 | } 63 | 64 | internal static HMACSHA512 CreateHMACSHA512(byte[] key) { 65 | return new HMACSHA512(key); 66 | } 67 | 68 | [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5350:MD5CannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")] 69 | [Obsolete("MD5 is deprecated and MUST NOT be used by new features. Consider using a SHA-2 algorithm instead.")] 70 | internal static MD5 CreateMD5() { 71 | return new MD5Cng(); 72 | } 73 | 74 | [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5354:SHA1CannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")] 75 | [Obsolete("SHA1 is deprecated and MUST NOT be used by new features. Consider using a SHA-2 algorithm instead.")] 76 | internal static SHA1 CreateSHA1() { 77 | return new SHA1Cng(); 78 | } 79 | 80 | internal static SHA256 CreateSHA256() { 81 | return new SHA256Cng(); 82 | } 83 | 84 | [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5353:TripleDESCannotBeUsed", Justification = @"This is only used by legacy code; new features do not use this algorithm.")] 85 | [Obsolete("3DES is deprecated and MUST NOT be used by new features. Consider using AES instead.")] 86 | internal static TripleDES CreateTripleDES() { 87 | return new TripleDESCryptoServiceProvider(); 88 | } 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /AspNetDerive/AspNetDerive.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7BEFDB83-2FD1-4C5C-9818-B96948F98378} 8 | Exe 9 | Properties 10 | LowLevelDesign.AspNetDerive 11 | AspNetDerive 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Hex\Hex.cs 48 | 49 | 50 | Hex\HexEncoder.cs 51 | 52 | 53 | Options.cs 54 | 55 | 56 | System.Web.Security.Cryptography\CryptoAlgorithms.cs 57 | 58 | 59 | System.Web.Security.Cryptography\CryptographicKey.cs 60 | 61 | 62 | System.Web.Security.Cryptography\CryptoUtil.cs 63 | 64 | 65 | System.Web.Security.Cryptography\IMasterKeyProvider.cs 66 | 67 | 68 | System.Web.Security.Cryptography\KeyDerivationFunction.cs 69 | 70 | 71 | System.Web.Security.Cryptography\Purpose.cs 72 | 73 | 74 | System.Web.Security.Cryptography\SP800_108.cs 75 | 76 | 77 | System.Web.Util\HttpEncoderUtility.cs 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 93 | -------------------------------------------------------------------------------- /AspNetCrypter/Hex/Hex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace LowLevelDesign.Hexify 6 | { 7 | public static class Hex 8 | { 9 | private readonly static HexEncoder encoder = new HexEncoder(); 10 | 11 | /// 12 | /// Returns hex representation of the byte array. 13 | /// 14 | /// bytes to encode 15 | /// 16 | public static string ToHexString(byte[] data) 17 | { 18 | return ToHexString(data, 0, data.Length); 19 | } 20 | 21 | /// 22 | /// Returns hex representation of the byte array. 23 | /// 24 | /// bytes to encode 25 | /// offset 26 | /// number of bytes to encode 27 | /// 28 | public static string ToHexString(byte[] data, int off, int length) 29 | { 30 | return Encoding.ASCII.GetString(Encode(data, off, length)); 31 | } 32 | 33 | private static byte[] Encode(byte[] data, int off, int length) 34 | { 35 | using (var stream = new MemoryStream()) { 36 | encoder.Encode(data, off, length, stream); 37 | return stream.ToArray(); 38 | } 39 | } 40 | 41 | /// 42 | /// Decodes hex representation to a byte array. 43 | /// 44 | /// hex string to decode 45 | /// 46 | public static byte[] FromHexString(string hex) 47 | { 48 | using (var stream = new MemoryStream()) { 49 | encoder.DecodeString(hex, stream); 50 | return stream.ToArray(); 51 | } 52 | } 53 | 54 | /// 55 | /// Returns a string containing a nice representation of the byte array 56 | /// (similarly to the binary editors). 57 | /// array of bytes to pretty print 58 | /// 59 | public static string PrettyPrint(byte[] bytes) 60 | { 61 | return PrettyPrint(bytes, 0, bytes.Length); 62 | } 63 | 64 | /// 65 | /// Returns a string containing a nice representation of the byte array 66 | /// (similarly to the binary editors). 67 | /// 68 | /// Example output: 69 | /// 70 | /// 0000: c8 83 93 8f b0 cb cb d3 d1 e5 7c ff 52 dc ea 92 E....ËËÓNa.yRÜe. 71 | /// 0010: 5b af 30 ca d8 7a 35 e9 2e 46 fa 85 b7 38 3f 4e [.0EOz5é.Fú.8?N 72 | /// 0020: 8d 60 af 4a 00 00 00 00 57 4d a4 29 35 9e c2 6f ...J....WM.)5.Âo 73 | /// 0030: 30 7b 92 40 33 6d 55 43 46 fe d6 8d ef 67 99 9c 0{.@3mUCF?Ö.ig.. 74 | /// 75 | /// array of bytes to pretty print 76 | /// offset in the array 77 | /// number of bytes to print 78 | /// 79 | public static string PrettyPrint(byte[] bytes, int offset, int length) 80 | { 81 | if (bytes.Length == 0) { 82 | return string.Empty; 83 | } 84 | 85 | var buffer = new StringBuilder(); 86 | int maxLength = offset + length; 87 | if (offset < 0 || offset >= bytes.Length || maxLength > bytes.Length) 88 | { 89 | throw new ArgumentException(); 90 | } 91 | 92 | int end = Math.Min(offset + 16, maxLength); 93 | int start = offset; 94 | 95 | while (end <= maxLength) { 96 | // print offset 97 | buffer.Append($"{(start - offset):x4}:"); 98 | 99 | // print hex bytes 100 | for (int i = start; i < end; i++) { 101 | buffer.Append($" {bytes[i]:x2}"); 102 | } 103 | for (int i = 0; i < 16 - (end - start); i++) { 104 | buffer.Append(" "); 105 | } 106 | 107 | buffer.Append(" "); 108 | // print ascii characters 109 | for (int i = start; i < end; i++) { 110 | char c = (char)bytes[i]; 111 | if (char.IsLetterOrDigit(c) || char.IsPunctuation(c)) { 112 | buffer.Append($"{c}"); 113 | } else { 114 | buffer.Append("."); 115 | } 116 | } 117 | 118 | if (end == maxLength) { 119 | break; 120 | } 121 | 122 | start = end; 123 | end = Math.Min(end + 16, maxLength); 124 | buffer.AppendLine(); 125 | } 126 | 127 | return buffer.ToString(); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /AspNetCrypter/Hex/HexEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace LowLevelDesign.Hexify 5 | { 6 | /** 7 | * Class imported from BouncyCastle library. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 10 | * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 11 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 13 | * OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | public class HexEncoder 16 | { 17 | protected readonly byte[] encodingTable = 18 | { 19 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', 20 | (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' 21 | }; 22 | 23 | /* 24 | * set up the decoding table. 25 | */ 26 | protected readonly byte[] decodingTable = new byte[128]; 27 | 28 | private static void FillArray(byte[] buf, byte b) 29 | { 30 | int i = buf.Length; 31 | while (i > 0) { 32 | buf[--i] = b; 33 | } 34 | } 35 | 36 | protected void InitialiseDecodingTable() 37 | { 38 | FillArray(decodingTable, (byte)0xff); 39 | 40 | for (int i = 0; i < encodingTable.Length; i++) { 41 | decodingTable[encodingTable[i]] = (byte)i; 42 | } 43 | 44 | decodingTable['A'] = decodingTable['a']; 45 | decodingTable['B'] = decodingTable['b']; 46 | decodingTable['C'] = decodingTable['c']; 47 | decodingTable['D'] = decodingTable['d']; 48 | decodingTable['E'] = decodingTable['e']; 49 | decodingTable['F'] = decodingTable['f']; 50 | } 51 | 52 | public HexEncoder() 53 | { 54 | InitialiseDecodingTable(); 55 | } 56 | 57 | /** 58 | * encode the input data producing a Hex output stream. 59 | * 60 | * @return the number of bytes produced. 61 | */ 62 | public int Encode(byte[] data, int off, int length, Stream outStream) 63 | { 64 | for (int i = off; i < (off + length); i++) { 65 | int v = data[i]; 66 | 67 | outStream.WriteByte(encodingTable[v >> 4]); 68 | outStream.WriteByte(encodingTable[v & 0xf]); 69 | } 70 | 71 | return length * 2; 72 | } 73 | 74 | private static bool Ignore(char c) 75 | { 76 | return c == '\n' || c == '\r' || c == '\t' || c == ' '; 77 | } 78 | 79 | /** 80 | * decode the Hex encoded byte data writing it to the given output stream, 81 | * whitespace characters will be ignored. 82 | * 83 | * @return the number of bytes produced. 84 | */ 85 | public int Decode(byte[] data, int off, int length, Stream outStream) 86 | { 87 | byte b1, b2; 88 | int outLen = 0; 89 | int end = off + length; 90 | 91 | while (end > off) { 92 | if (!Ignore((char)data[end - 1])) { 93 | break; 94 | } 95 | 96 | end--; 97 | } 98 | 99 | int i = off; 100 | while (i < end) { 101 | while (i < end && Ignore((char)data[i])) { 102 | i++; 103 | } 104 | 105 | b1 = decodingTable[data[i++]]; 106 | 107 | while (i < end && Ignore((char)data[i])) { 108 | i++; 109 | } 110 | 111 | b2 = decodingTable[data[i++]]; 112 | 113 | if ((b1 | b2) >= 0x80) 114 | throw new IOException("invalid characters encountered in Hex data"); 115 | 116 | outStream.WriteByte((byte)((b1 << 4) | b2)); 117 | 118 | outLen++; 119 | } 120 | 121 | return outLen; 122 | } 123 | 124 | /** 125 | * decode the Hex encoded string data writing it to the given output stream, 126 | * whitespace characters will be ignored. 127 | * 128 | * @return the number of bytes produced. 129 | */ 130 | public int DecodeString(string data, Stream outStream) 131 | { 132 | byte b1, b2; 133 | int length = 0; 134 | 135 | int end = data.Length; 136 | 137 | while (end > 0) { 138 | if (!Ignore(data[end - 1])) { 139 | break; 140 | } 141 | 142 | end--; 143 | } 144 | 145 | int i = 0; 146 | while (i < end) { 147 | while (i < end && Ignore(data[i])) { 148 | i++; 149 | } 150 | 151 | b1 = decodingTable[data[i++]]; 152 | 153 | while (i < end && Ignore(data[i])) { 154 | i++; 155 | } 156 | 157 | b2 = decodingTable[data[i++]]; 158 | 159 | if ((b1 | b2) >= 0x80) 160 | throw new IOException("invalid characters encountered in Hex data"); 161 | 162 | outStream.WriteByte((byte)((b1 << 4) | b2)); 163 | 164 | length++; 165 | } 166 | 167 | return length; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /AspNetCrypter/Program.cs: -------------------------------------------------------------------------------- 1 | using NDesk.Options; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Web.Security.Cryptography; 9 | using System.Web.Util; 10 | 11 | namespace LowLevelDesign.AspNetCrypter 12 | { 13 | public static class Program 14 | { 15 | private static readonly Dictionary purposeMap = new Dictionary(StringComparer.Ordinal) { 16 | { "owin.cookie", Purpose.User_MachineKey_Protect.AppendSpecificPurposes( 17 | new [] { 18 | "Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware", 19 | "ApplicationCookie", 20 | "v1" 21 | }) } 22 | }; 23 | 24 | public static void Main(string[] args) 25 | { 26 | string validationKeyAsText = null, decryptionKeyAsText = null, 27 | textToDecrypt = null, purposeKey = null, fileToDecrypt = null; 28 | bool showhelp = false, isBase64 = false; 29 | 30 | var p = new OptionSet 31 | { 32 | { "vk=", "the validation key (in hex)", v => validationKeyAsText = v }, 33 | { "dk=", "the decryption key (in hex)", v => decryptionKeyAsText = v }, 34 | { "p|purpose=", "the encryption context\n(currently only: owin.cookie)", v => purposeKey = v }, 35 | { "base64", "data is provided in base64 format (otherwise we assume hex)", v => isBase64 = v != null }, 36 | { "i|input=", "input data file (when too big for command line)", v => fileToDecrypt = v }, 37 | { "h|help", "Show this message and exit", v => showhelp = v != null }, 38 | { "?", "Show this message and exit", v => showhelp = v != null } 39 | }; 40 | 41 | try { 42 | textToDecrypt = p.Parse(args).FirstOrDefault(); 43 | if (textToDecrypt == null && fileToDecrypt != null) 44 | { 45 | if (!File.Exists(fileToDecrypt)) 46 | throw new OptionException("input file does not exist", "input"); 47 | textToDecrypt = File.ReadAllText(fileToDecrypt); 48 | } 49 | 50 | } 51 | catch (OptionException ex) { 52 | Console.Error.Write("ERROR: invalid argument, "); 53 | Console.Error.WriteLine(ex.Message); 54 | Console.Error.WriteLine(); 55 | showhelp = true; 56 | } 57 | if (!showhelp && textToDecrypt == null) { 58 | Console.Error.WriteLine("ERROR: please provide data to decrypt"); 59 | Console.Error.WriteLine(); 60 | showhelp = true; 61 | } 62 | if (!showhelp && (validationKeyAsText == null || decryptionKeyAsText == null || purposeKey == null)) { 63 | Console.Error.WriteLine("ERROR: all parameters are required"); 64 | Console.Error.WriteLine(); 65 | showhelp = true; 66 | } 67 | Purpose purpose = null; 68 | if (!showhelp && !purposeMap.TryGetValue(purposeKey, out purpose)) { 69 | Console.Error.WriteLine("ERROR: invalid purpose"); 70 | Console.Error.WriteLine(); 71 | showhelp = true; 72 | } 73 | if (showhelp) { 74 | ShowHelp(p); 75 | return; 76 | } 77 | Debug.Assert(purpose != null); 78 | Debug.Assert(textToDecrypt != null); 79 | Debug.Assert(decryptionKeyAsText != null); 80 | Debug.Assert(validationKeyAsText != null); 81 | 82 | byte[] encryptedData; 83 | if (isBase64) { 84 | try { 85 | encryptedData = HttpEncoder.Default.UrlTokenDecode(textToDecrypt); 86 | } catch (FormatException) { 87 | encryptedData = null; 88 | } 89 | } else { 90 | if (textToDecrypt.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { 91 | textToDecrypt = textToDecrypt.Substring(2); 92 | } 93 | encryptedData = CryptoUtil.HexToBinary(textToDecrypt); 94 | } 95 | byte[] decryptionKey = CryptoUtil.HexToBinary(decryptionKeyAsText.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? 96 | decryptionKeyAsText.Substring(2) : decryptionKeyAsText); 97 | byte[] validationKey = CryptoUtil.HexToBinary(validationKeyAsText.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? 98 | validationKeyAsText.Substring(2) : validationKeyAsText); 99 | if (decryptionKey == null || validationKey == null) { 100 | Console.Error.WriteLine("ERROR: invalid encryption or validation key"); 101 | Console.Error.WriteLine(); 102 | return; 103 | } 104 | 105 | if (encryptedData == null) { 106 | Console.Error.WriteLine("ERROR: invalid data to decrypt - must be either base64 or hex"); 107 | Console.Error.WriteLine(); 108 | return; 109 | } 110 | 111 | Console.WriteLine(); 112 | var decryptor = new AspNetDecryptor(purpose, new CryptographicKey(decryptionKey), new CryptographicKey(validationKey), 113 | "owin.cookie".Equals(purposeKey, StringComparison.Ordinal)); 114 | var decryptedData = decryptor.DecryptData(encryptedData); 115 | Console.WriteLine(Hexify.Hex.PrettyPrint(decryptedData)); 116 | Console.WriteLine(); 117 | } 118 | 119 | static void ShowHelp(OptionSet p) 120 | { 121 | Console.WriteLine("{0} v{1} - {2}", Assembly.GetExecutingAssembly().GetCustomAttribute().Title, 122 | Assembly.GetExecutingAssembly().GetName().Version.ToString(), 123 | Assembly.GetExecutingAssembly().GetCustomAttribute().Description); 124 | Console.WriteLine(Assembly.GetExecutingAssembly().GetCustomAttribute().Copyright); 125 | Console.WriteLine(); 126 | Console.WriteLine("Usage: aspnetcrypter [OPTIONS] encrypteddata"); 127 | Console.WriteLine(); 128 | Console.WriteLine("Options:"); 129 | p.WriteOptionDescriptions(Console.Out); 130 | Console.WriteLine(); 131 | 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/SP800_108.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | using System.Security.Cryptography; 10 | 11 | /****************************************************************** 12 | * !! WARNING !! * 13 | * This class contains cryptographic code. If you make changes to * 14 | * this class, please have it reviewed by the appropriate people. * 15 | ******************************************************************/ 16 | 17 | // Implements the NIST SP800-108 key derivation routine in counter mode with an HMAC PRF (HMACSHA512). 18 | // See: http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf 19 | // 20 | // The algorithm is defined as follows: 21 | // 22 | // INPUTS: 23 | // PRF = The pseudo-random function used for key derivation; in our case, an HMAC. 24 | // KI = The key derivation key (master key) from which keys will be derived. 25 | // Label = The purpose of the derived key. 26 | // Context = Information related to the derived key, such as consuming party identities or a nonce. 27 | // L = The desired length (in bits) of the derived key. 28 | // 29 | // ALGORITHM: 30 | // Let n = ceil(L / HMAC-output-size) 31 | // For i = 1 to n, 32 | // K_i = PRF(KI, [i]_2 || Label || 0x00 || Context || [L]_2) 33 | // where [x]_2 = the big-endian representation of 'x' 34 | // 35 | // OUTPUT: 36 | // Result := K_1 || K_2 || ... || K_n, truncated to be L bits in length 37 | 38 | internal static class SP800_108 { 39 | 40 | // Implements the KeyDerivationFunction delegate signature; public entry point to the API. 41 | public static CryptographicKey DeriveKey(CryptographicKey keyDerivationKey, Purpose purpose) { 42 | // After consultation with the crypto board, we have decided to use HMACSHA512 as the PRF 43 | // to our KDF. The reason for this is that our PRF is an HMAC, so the total entropy of the 44 | // PRF is given by MIN(key derivation key length, HMAC block size). It is conceivable that 45 | // a developer might specify a key greater than 256 bits in length, at which point using 46 | // a shorter PRF like HMACSHA256 starts discarding entropy. But from the crypto team's 47 | // perspective it is unreasonable for a developer to supply a key greater than 512 bits, 48 | // so there's no real harm in us limiting our PRF entropy to 512 bits (HMACSHA512). 49 | // 50 | // On 64-bit platforms, HMACSHA512 matches or outperforms HMACSHA256 in our perf testing. 51 | // On 32-bit platforms, HMACSHA512 is around 1/3 the speed of HMACSHA256. In both cases, we 52 | // try to cache the derived CryptographicKey wherever we can, so this shouldn't be a 53 | // bottleneck regardless. 54 | 55 | using (HMACSHA512 hmac = CryptoAlgorithms.CreateHMACSHA512(keyDerivationKey.GetKeyMaterial())) { 56 | byte[] label, context; 57 | purpose.GetKeyDerivationParameters(out label, out context); 58 | 59 | byte[] derivedKey = DeriveKeyImpl(hmac, label, context, keyDerivationKey.KeyLength); 60 | return new CryptographicKey(derivedKey); 61 | } 62 | } 63 | 64 | // NOTE: This method also exists in Win8 (as BCryptKeyDerivation) and QTD (as DeriveKeySP800_108). 65 | // However, the QTD implementation is currently incorrect, so we can't depend on it here. The below 66 | // is a correct implementation. When we take a Win8 dependency, we can call into BCryptKeyDerivation. 67 | private static byte[] DeriveKeyImpl(HMAC hmac, byte[] label, byte[] context, int keyLengthInBits) { 68 | // This entire method is checked because according to SP800-108 it is an error 69 | // for any single operation to result in overflow. 70 | checked { 71 | 72 | // Make a buffer which is ____ || label || 0x00 || context || [l]_2. 73 | // We can reuse this buffer during each round. 74 | 75 | int labelLength = (label != null) ? label.Length : 0; 76 | int contextLength = (context != null) ? context.Length : 0; 77 | byte[] buffer = new byte[4 /* [i]_2 */ + labelLength /* label */ + 1 /* 0x00 */ + contextLength /* context */ + 4 /* [L]_2 */]; 78 | 79 | if (labelLength != 0) { 80 | Buffer.BlockCopy(label, 0, buffer, 4, labelLength); // the 4 accounts for the [i]_2 length 81 | } 82 | if (contextLength != 0) { 83 | Buffer.BlockCopy(context, 0, buffer, 5 + labelLength, contextLength); // the '5 +' accounts for the [i]_2 length, the label, and the 0x00 byte 84 | } 85 | WriteUInt32ToByteArrayBigEndian((uint)keyLengthInBits, buffer, 5 + labelLength + contextLength); // the '5 +' accounts for the [i]_2 length, the label, the 0x00 byte, and the context 86 | 87 | // Initialization 88 | 89 | int numBytesWritten = 0; 90 | int numBytesRemaining = keyLengthInBits / 8; 91 | byte[] output = new byte[numBytesRemaining]; 92 | 93 | // Calculate each K_i value and copy the leftmost bits to the output buffer as appropriate. 94 | 95 | for (uint i = 1; numBytesRemaining > 0; i++) { 96 | WriteUInt32ToByteArrayBigEndian(i, buffer, 0); // set the first 32 bits of the buffer to be the current iteration value 97 | byte[] K_i = hmac.ComputeHash(buffer); 98 | 99 | // copy the leftmost bits of K_i into the output buffer 100 | int numBytesToCopy = Math.Min(numBytesRemaining, K_i.Length); 101 | Buffer.BlockCopy(K_i, 0, output, numBytesWritten, numBytesToCopy); 102 | numBytesWritten += numBytesToCopy; 103 | numBytesRemaining -= numBytesToCopy; 104 | } 105 | 106 | // finished 107 | return output; 108 | } 109 | } 110 | 111 | private static void WriteUInt32ToByteArrayBigEndian(uint value, byte[] buffer, int offset) { 112 | buffer[offset + 0] = (byte)(value >> 24); 113 | buffer[offset + 1] = (byte)(value >> 16); 114 | buffer[offset + 2] = (byte)(value >> 8); 115 | buffer[offset + 3] = (byte)(value); 116 | } 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/CryptoUtil.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | using System.Runtime.CompilerServices; 10 | using System.Security.Cryptography; 11 | using System.Text; 12 | using Util; 13 | 14 | // Contains helper methods for dealing with cryptographic operations. 15 | 16 | internal static class CryptoUtil { 17 | 18 | /// 19 | /// Similar to Encoding.UTF8, but throws on invalid bytes. Useful for security routines where we need 20 | /// strong guarantees that we're always producing valid UTF8 streams. 21 | /// 22 | public static readonly UTF8Encoding SecureUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); 23 | 24 | /// 25 | /// Converts a byte array into its hexadecimal representation. 26 | /// 27 | /// The binary byte array. 28 | /// The hexadecimal (uppercase) equivalent of the byte array. 29 | public static string BinaryToHex(byte[] data) { 30 | if (data == null) { 31 | return null; 32 | } 33 | 34 | char[] hex = new char[checked(data.Length * 2)]; 35 | 36 | for (int i = 0; i < data.Length; i++) { 37 | byte thisByte = data[i]; 38 | hex[2 * i] = NibbleToHex((byte)(thisByte >> 4)); // high nibble 39 | hex[2 * i + 1] = NibbleToHex((byte)(thisByte & 0xf)); // low nibble 40 | } 41 | 42 | return new string(hex); 43 | } 44 | 45 | // Determines if two buffer instances are equal, e.g. whether they contain the same payload. This method 46 | // is written in such a manner that it should take the same amount of time to execute regardless of 47 | // whether the result is success or failure. The modulus operation is intended to make the check take the 48 | // same amount of time, even if the buffers are of different lengths. 49 | // 50 | // !! DO NOT CHANGE THIS METHOD WITHOUT SECURITY 51 | [MethodImpl(MethodImplOptions.NoOptimization)] 52 | public static bool BuffersAreEqual(byte[] buffer1, int buffer1Offset, int buffer1Count, byte[] buffer2, int buffer2Offset, int buffer2Count) { 53 | bool success = (buffer1Count == buffer2Count); // can't possibly be successful if the buffers are of different lengths 54 | for (int i = 0; i < buffer1Count; i++) { 55 | success &= (buffer1[buffer1Offset + i] == buffer2[buffer2Offset + (i % buffer2Count)]); 56 | } 57 | return success; 58 | } 59 | 60 | /// 61 | /// Computes the SHA256 hash of a given input. 62 | /// 63 | /// The input over which to compute the hash. 64 | /// The binary hash (32 bytes) of the input. 65 | public static byte[] ComputeSHA256Hash(byte[] input) { 66 | return ComputeSHA256Hash(input, 0, input.Length); 67 | } 68 | 69 | /// 70 | /// Computes the SHA256 hash of a given segment in a buffer. 71 | /// 72 | /// The buffer over which to compute the hash. 73 | /// The offset at which to begin computing the hash. 74 | /// The number of bytes in the buffer to include in the hash. 75 | /// The binary hash (32 bytes) of the buffer segment. 76 | public static byte[] ComputeSHA256Hash(byte[] buffer, int offset, int count) { 77 | using (SHA256 sha256 = CryptoAlgorithms.CreateSHA256()) { 78 | return sha256.ComputeHash(buffer, offset, count); 79 | } 80 | } 81 | 82 | /// 83 | /// Returns an IV that's based solely on the contents of a buffer; useful for generating 84 | /// predictable IVs for ciphertexts that need to be cached. The output value is only 85 | /// appropriate for use as an IV and must not be used for any other purpose. 86 | /// 87 | /// This method uses an iterated unkeyed SHA256 to calculate the IV. 88 | /// The input buffer over which to calculate the IV. 89 | /// The requested length (in bits) of the IV to generate. 90 | /// The calculated IV. 91 | public static byte[] CreatePredictableIV(byte[] buffer, int ivBitLength) { 92 | // Algorithm: 93 | // T_0 = SHA256(buffer) 94 | // T_n = SHA256(T_{n-1}) 95 | // output = T_0 || T_1 || ... || T_n (as many blocks as necessary to reach ivBitLength) 96 | 97 | byte[] output = new byte[ivBitLength / 8]; 98 | int bytesCopied = 0; 99 | int bytesRemaining = output.Length; 100 | 101 | using (SHA256 sha256 = CryptoAlgorithms.CreateSHA256()) { 102 | while (bytesRemaining > 0) { 103 | byte[] hashed = sha256.ComputeHash(buffer); 104 | 105 | int bytesToCopy = Math.Min(bytesRemaining, hashed.Length); 106 | Buffer.BlockCopy(hashed, 0, output, bytesCopied, bytesToCopy); 107 | 108 | bytesCopied += bytesToCopy; 109 | bytesRemaining -= bytesToCopy; 110 | 111 | buffer = hashed; // next iteration (if it occurs) will operate over the block just hashed 112 | } 113 | } 114 | 115 | return output; 116 | } 117 | 118 | /// 119 | /// Converts a hexadecimal string into its binary representation. 120 | /// 121 | /// The hex string. 122 | /// The byte array corresponding to the contents of the hex string, 123 | /// or null if the input string is not a valid hex string. 124 | public static byte[] HexToBinary(string data) { 125 | if (data == null || data.Length % 2 != 0) { 126 | // input string length is not evenly divisible by 2 127 | return null; 128 | } 129 | 130 | byte[] binary = new byte[data.Length / 2]; 131 | 132 | for (int i = 0; i < binary.Length; i++) { 133 | int highNibble = HttpEncoderUtility.HexToInt(data[2 * i]); 134 | int lowNibble = HttpEncoderUtility.HexToInt(data[2 * i + 1]); 135 | 136 | if (highNibble == -1 || lowNibble == -1) { 137 | return null; // bad hex data 138 | } 139 | binary[i] = (byte)((highNibble << 4) | lowNibble); 140 | } 141 | 142 | return binary; 143 | } 144 | 145 | // converts a nibble (4 bits) to its uppercase hexadecimal character representation [0-9, A-F] 146 | private static char NibbleToHex(byte nibble) { 147 | return (char)((nibble < 10) ? (nibble + '0') : (nibble - 10 + 'A')); 148 | } 149 | 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/Purpose.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | using System.Collections.Generic; 10 | using System.IO; 11 | using System.Text; 12 | 13 | // Represents a purpose that can be passed to a cryptographic routine to control key derivation / ciphertext modification. 14 | // This is hardening the crypto routines to prevent playing ciphertext off of components that didn't generate them. 15 | // 16 | // !! IMPORTANT !! 17 | // The built-in purposes do not contain privileged information and are not meant to be treated as secrets. Any external 18 | // person can disassemble our code or look directly at our source to see what our Purpose objects are used for. 19 | // 20 | // PrimaryPurpose: This is a well-known string that identifies the reason for this Purpose. The pattern we use 21 | // is that PrimaryPurpose is the name of the consumer, making each consumer's Purpose unique. 22 | // 23 | // SpecificPurposes: These are extra optional strings that further differentiate Purpose objects that might have the 24 | // same PrimaryPurpose. The pattern we use is that if a single consumer has multiple Purposes, he can use 25 | // SpecificPurposes to uniquely identify them. The information here is generally not secret (we can put the type of 26 | // the currently executing Page here, for example), but it is valid to seed this property with a secret obtained 27 | // at runtime (such as a nonce shared between two parties). 28 | 29 | internal sealed class Purpose { 30 | 31 | // predefined purposes 32 | public static readonly Purpose AnonymousIdentificationModule_Ticket = new Purpose("AnonymousIdentificationModule.Ticket"); 33 | public static readonly Purpose AssemblyResourceLoader_WebResourceUrl = new Purpose("AssemblyResourceLoader.WebResourceUrl"); 34 | public static readonly Purpose FormsAuthentication_Ticket = new Purpose("FormsAuthentication.Ticket"); 35 | public static readonly Purpose WebForms_Page_PreviousPageID = new Purpose("WebForms.Page.PreviousPageID"); 36 | public static readonly Purpose RolePrincipal_Ticket = new Purpose("RolePrincipal.Ticket"); 37 | public static readonly Purpose ScriptResourceHandler_ScriptResourceUrl = new Purpose("ScriptResourceHandler.ScriptResourceUrl"); 38 | 39 | // predefined ViewState purposes; they won't be used as-is (they're combined with the page information) 40 | public static readonly Purpose WebForms_ClientScriptManager_EventValidation = new Purpose("WebForms.ClientScriptManager.EventValidation"); 41 | public static readonly Purpose WebForms_DetailsView_KeyTable = new Purpose("WebForms.DetailsView.KeyTable"); 42 | public static readonly Purpose WebForms_GridView_DataKeys = new Purpose("WebForms.GridView.DataKeys"); 43 | public static readonly Purpose WebForms_GridView_SortExpression = new Purpose("WebForms.GridView.SortExpression"); 44 | public static readonly Purpose WebForms_HiddenFieldPageStatePersister_ClientState = new Purpose("WebForms.HiddenFieldPageStatePersister.ClientState"); 45 | public static readonly Purpose WebForms_ScriptManager_HistoryState = new Purpose("WebForms.ScriptManager.HistoryState"); 46 | public static readonly Purpose WebForms_SessionPageStatePersister_ClientState = new Purpose("WebForms.SessionPageStatePersister.ClientState"); 47 | 48 | // predefined miscellaneoous purposes; they won't be used as-is (they're combined with other specificPurposes) 49 | public static readonly Purpose User_MachineKey_Protect = new Purpose("User.MachineKey.Protect"); // used by the MachineKey static class Protect / Unprotect methods 50 | public static readonly Purpose User_ObjectStateFormatter_Serialize = new Purpose("User.ObjectStateFormatter.Serialize"); // used by ObjectStateFormatter.Serialize() if called manually 51 | 52 | public readonly string PrimaryPurpose; 53 | public readonly string[] SpecificPurposes; 54 | 55 | private byte[] _derivedKeyLabel; 56 | private byte[] _derivedKeyContext; 57 | 58 | public Purpose(string primaryPurpose, params string[] specificPurposes) 59 | : this(primaryPurpose, specificPurposes, null, null) { 60 | } 61 | 62 | // ctor for unit testing 63 | internal Purpose(string primaryPurpose, string[] specificPurposes, CryptographicKey derivedEncryptionKey, CryptographicKey derivedValidationKey) { 64 | PrimaryPurpose = primaryPurpose; 65 | SpecificPurposes = specificPurposes ?? new string[0]; 66 | DerivedEncryptionKey = derivedEncryptionKey; 67 | DerivedValidationKey = derivedValidationKey; 68 | SaveDerivedKeys = (SpecificPurposes.Length == 0); 69 | } 70 | 71 | // The cryptographic keys that were derived from this Purpose. 72 | internal CryptographicKey DerivedEncryptionKey { get; private set; } 73 | internal CryptographicKey DerivedValidationKey { get; private set; } 74 | 75 | // Whether the derived key should be saved back to this Purpose object by the ICryptoService, 76 | // e.g. because this Purpose will be used over and over again. We assume that any built-in 77 | // Purpose object that is passed without any specific purposes is intended for repeated use, 78 | // hence the ICryptoService will try to cache cryptographic keys as a performance optimization. 79 | // If specific purposes have been specified, they were likely generated at runtime, hence it 80 | // is not appropriate for the keys to be cached in this instance. 81 | internal bool SaveDerivedKeys { get; set; } 82 | 83 | // Returns a new Purpose which is the specified Purpose plus the specified SpecificPurpose. 84 | // Leaves the original Purpose unmodified. 85 | internal Purpose AppendSpecificPurpose(string specificPurpose) { 86 | // Append the specified specificPurpose to the existing list 87 | string[] newSpecificPurposes = new string[SpecificPurposes.Length + 1]; 88 | Array.Copy(SpecificPurposes, newSpecificPurposes, SpecificPurposes.Length); 89 | newSpecificPurposes[newSpecificPurposes.Length - 1] = specificPurpose; 90 | return new Purpose(PrimaryPurpose, newSpecificPurposes); 91 | } 92 | 93 | // Returns a new Purpose which is the specified Purpose plus the specified SpecificPurposes. 94 | // Leaves the original Purpose unmodified. 95 | internal Purpose AppendSpecificPurposes(IList specificPurposes) { 96 | // No specific purposes to add 97 | if (specificPurposes == null || specificPurposes.Count == 0) { 98 | return this; 99 | } 100 | 101 | // Append the specified specificPurposes to the existing list 102 | string[] newSpecificPurposes = new string[SpecificPurposes.Length + specificPurposes.Count]; 103 | Array.Copy(SpecificPurposes, newSpecificPurposes, SpecificPurposes.Length); 104 | specificPurposes.CopyTo(newSpecificPurposes, SpecificPurposes.Length); 105 | return new Purpose(PrimaryPurpose, newSpecificPurposes); 106 | } 107 | 108 | public CryptographicKey GetDerivedEncryptionKey(IMasterKeyProvider masterKeyProvider, KeyDerivationFunction keyDerivationFunction) { 109 | // has a key already been stored? 110 | CryptographicKey actualDerivedKey = DerivedEncryptionKey; 111 | if (actualDerivedKey == null) { 112 | CryptographicKey masterKey = masterKeyProvider.GetEncryptionKey(); 113 | actualDerivedKey = keyDerivationFunction(masterKey, this); 114 | 115 | // only save the key back to storage if this Purpose is configured to do so 116 | if (SaveDerivedKeys) { 117 | DerivedEncryptionKey = actualDerivedKey; 118 | } 119 | } 120 | 121 | return actualDerivedKey; 122 | } 123 | 124 | public CryptographicKey GetDerivedValidationKey(IMasterKeyProvider masterKeyProvider, KeyDerivationFunction keyDerivationFunction) { 125 | // has a key already been stored? 126 | CryptographicKey actualDerivedKey = DerivedValidationKey; 127 | if (actualDerivedKey == null) { 128 | CryptographicKey masterKey = masterKeyProvider.GetValidationKey(); 129 | actualDerivedKey = keyDerivationFunction(masterKey, this); 130 | 131 | // only save the key back to storage if this Purpose is configured to do so 132 | if (SaveDerivedKeys) { 133 | DerivedValidationKey = actualDerivedKey; 134 | } 135 | } 136 | 137 | return actualDerivedKey; 138 | } 139 | 140 | // Returns a label and context suitable for passing into the SP800-108 KDF. 141 | internal void GetKeyDerivationParameters(out byte[] label, out byte[] context) { 142 | // The primary purpose can just be used as the label directly, since ASP.NET 143 | // is always in full control of the primary purpose (it's never user-specified). 144 | if (_derivedKeyLabel == null) { 145 | _derivedKeyLabel = CryptoUtil.SecureUTF8Encoding.GetBytes(PrimaryPurpose); 146 | } 147 | 148 | // The specific purposes (which can contain nonce, identity, etc.) are concatenated 149 | // together to form the context. The BinaryWriter class prepends each element with 150 | // a 7-bit encoded length to guarantee uniqueness. 151 | if (_derivedKeyContext == null) { 152 | using (MemoryStream stream = new MemoryStream()) 153 | using (BinaryWriter writer = new BinaryWriter(stream, CryptoUtil.SecureUTF8Encoding)) { 154 | foreach (string specificPurpose in SpecificPurposes) { 155 | writer.Write(specificPurpose); 156 | } 157 | _derivedKeyContext = stream.ToArray(); 158 | } 159 | } 160 | 161 | label = _derivedKeyLabel; 162 | context = _derivedKeyContext; 163 | } 164 | 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Security.Cryptography/NetFXCryptoService.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | namespace System.Web.Security.Cryptography { 8 | using System; 9 | using System.IO; 10 | using System.Security.Cryptography; 11 | 12 | /****************************************************************** 13 | * !! WARNING !! * 14 | * This class contains cryptographic code. If you make changes to * 15 | * this class, please have it reviewed by the appropriate people. * 16 | ******************************************************************/ 17 | 18 | // Uses .NET Framework classes to encrypt (SymmetricAlgorithm) and sign (KeyedHashAlgorithm) data. 19 | // 20 | // [PROTECT] 21 | // INPUT: clearData 22 | // OUTPUT: protectedData 23 | // ALGORITHM: 24 | // protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData)) 25 | // 26 | // [UNPROTECT] 27 | // INPUT: protectedData 28 | // OUTPUT: clearData 29 | // ALGORITHM: 30 | // 1) Assume protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData)) 31 | // 2) Validate the signature over the payload and strip it from the end 32 | // 3) Strip off the IV from the beginning of the payload 33 | // 4) Decrypt what remains of the payload, and return it as clearData 34 | 35 | internal sealed class NetFXCryptoService : ICryptoService { 36 | 37 | private readonly ICryptoAlgorithmFactory _cryptoAlgorithmFactory; 38 | private readonly CryptographicKey _encryptionKey; 39 | private readonly bool _predictableIV; 40 | private readonly CryptographicKey _validationKey; 41 | 42 | public NetFXCryptoService(ICryptoAlgorithmFactory cryptoAlgorithmFactory, CryptographicKey encryptionKey, CryptographicKey validationKey, bool predictableIV = false) { 43 | _cryptoAlgorithmFactory = cryptoAlgorithmFactory; 44 | _encryptionKey = encryptionKey; 45 | _validationKey = validationKey; 46 | _predictableIV = predictableIV; 47 | } 48 | 49 | public byte[] Protect(byte[] clearData) { 50 | // The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures. 51 | checked { 52 | 53 | // These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block. 54 | using (SymmetricAlgorithm encryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) { 55 | // Initialize the algorithm with the specified key and an appropriate IV 56 | encryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial(); 57 | 58 | if (_predictableIV) { 59 | // The caller wanted the output to be predictable (e.g. for caching), so we'll create an 60 | // appropriate IV directly from the input buffer. The IV length is equal to the block size. 61 | encryptionAlgorithm.IV = CryptoUtil.CreatePredictableIV(clearData, encryptionAlgorithm.BlockSize); 62 | } 63 | else { 64 | // If the caller didn't ask for a predictable IV, just let the algorithm itself choose one. 65 | encryptionAlgorithm.GenerateIV(); 66 | } 67 | byte[] iv = encryptionAlgorithm.IV; 68 | 69 | using (MemoryStream memStream = new MemoryStream()) { 70 | memStream.Write(iv, 0, iv.Length); 71 | 72 | // At this point: 73 | // memStream := IV 74 | 75 | // Write the encrypted payload to the memory stream. 76 | using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor()) { 77 | using (CryptoStream cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write)) { 78 | cryptoStream.Write(clearData, 0, clearData.Length); 79 | cryptoStream.FlushFinalBlock(); 80 | 81 | // At this point: 82 | // memStream := IV || Enc(Kenc, IV, clearData) 83 | 84 | // These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block. 85 | using (KeyedHashAlgorithm signingAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) { 86 | // Initialize the algorithm with the specified key 87 | signingAlgorithm.Key = _validationKey.GetKeyMaterial(); 88 | 89 | // Compute the signature 90 | byte[] signature = signingAlgorithm.ComputeHash(memStream.GetBuffer(), 0, (int)memStream.Length); 91 | 92 | // At this point: 93 | // memStream := IV || Enc(Kenc, IV, clearData) 94 | // signature := Sign(Kval, IV || Enc(Kenc, IV, clearData)) 95 | 96 | // Append the signature to the encrypted payload 97 | memStream.Write(signature, 0, signature.Length); 98 | 99 | // At this point: 100 | // memStream := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData)) 101 | 102 | // Algorithm complete 103 | byte[] protectedData = memStream.ToArray(); 104 | return protectedData; 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | public byte[] Unprotect(byte[] protectedData) { 114 | // The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures. 115 | checked { 116 | 117 | // We want to check that the input is in the form: 118 | // protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData)) 119 | 120 | // Definitions used in this method: 121 | // encryptedPayload := Enc(Kenc, IV, clearData) 122 | // signature := Sign(Kval, IV || encryptedPayload) 123 | 124 | // These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block. 125 | using (SymmetricAlgorithm decryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) { 126 | decryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial(); 127 | 128 | // These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block. 129 | using (KeyedHashAlgorithm validationAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) { 130 | validationAlgorithm.Key = _validationKey.GetKeyMaterial(); 131 | 132 | // First, we need to verify that protectedData is even long enough to contain 133 | // the required components (IV, encryptedPayload, signature). 134 | 135 | int ivByteCount = decryptionAlgorithm.BlockSize / 8; // IV length is equal to the block size 136 | int signatureByteCount = validationAlgorithm.HashSize / 8; 137 | int encryptedPayloadByteCount = protectedData.Length - ivByteCount - signatureByteCount; 138 | if (encryptedPayloadByteCount <= 0) { 139 | // protectedData doesn't meet minimum length requirements 140 | return null; 141 | } 142 | 143 | // If that check passes, we need to detect payload tampering. 144 | 145 | // Compute the signature over the IV and encrypted payload 146 | // computedSignature := Sign(Kval, IV || encryptedPayload) 147 | byte[] computedSignature = validationAlgorithm.ComputeHash(protectedData, 0, ivByteCount + encryptedPayloadByteCount); 148 | 149 | if (!CryptoUtil.BuffersAreEqual( 150 | buffer1: protectedData, buffer1Offset: ivByteCount + encryptedPayloadByteCount, buffer1Count: signatureByteCount, 151 | buffer2: computedSignature, buffer2Offset: 0, buffer2Count: computedSignature.Length)) { 152 | 153 | // the computed signature didn't match the incoming signature, which is a sign of payload tampering 154 | return null; 155 | } 156 | 157 | // At this point, we're certain that we generated the signature over this payload, 158 | // so we can go ahead with decryption. 159 | 160 | // Populate the IV from the incoming stream 161 | byte[] iv = new byte[ivByteCount]; 162 | Buffer.BlockCopy(protectedData, 0, iv, 0, iv.Length); 163 | decryptionAlgorithm.IV = iv; 164 | 165 | // Write the decrypted payload to the memory stream. 166 | using (MemoryStream memStream = new MemoryStream()) { 167 | using (ICryptoTransform decryptor = decryptionAlgorithm.CreateDecryptor()) { 168 | using (CryptoStream cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Write)) { 169 | cryptoStream.Write(protectedData, ivByteCount, encryptedPayloadByteCount); 170 | cryptoStream.FlushFinalBlock(); 171 | 172 | // At this point 173 | // memStream := clearData 174 | 175 | byte[] clearData = memStream.ToArray(); 176 | return clearData; 177 | } 178 | } 179 | } 180 | } 181 | } 182 | } 183 | } 184 | 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /AspNetCrypter/System.Web.Util/HttpEncoder.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Microsoft Corporation. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | /* 8 | * Base class providing extensibility hooks for custom encoding / decoding 9 | * 10 | * Copyright (c) 2009 Microsoft Corporation 11 | */ 12 | 13 | namespace System.Web.Util 14 | { 15 | using Diagnostics; 16 | using System; 17 | using System.Diagnostics.CodeAnalysis; 18 | using System.Globalization; 19 | using System.IO; 20 | using System.Net; 21 | using System.Text; 22 | 23 | public class HttpEncoder { 24 | 25 | private readonly bool _isDefaultEncoder; 26 | 27 | private static readonly HttpEncoder _defaultEncoder = new HttpEncoder(); 28 | 29 | private static readonly string[] _headerEncodingTable = new string[] { 30 | "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", 31 | "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", 32 | "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", 33 | "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f" 34 | }; 35 | 36 | public HttpEncoder() { 37 | _isDefaultEncoder = (GetType() == typeof(HttpEncoder)); 38 | } 39 | 40 | public static HttpEncoder Default { 41 | get { 42 | return _defaultEncoder; 43 | } 44 | } 45 | 46 | internal virtual bool JavaScriptEncodeAmpersand { 47 | get { 48 | return true; // CHANGED: !AppSettings.JavaScriptDoNotEncodeAmpersand; 49 | } 50 | } 51 | 52 | private static void AppendCharAsUnicodeJavaScript(StringBuilder builder, char c) { 53 | builder.Append("\\u"); 54 | builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture)); 55 | } 56 | 57 | private bool CharRequiresJavaScriptEncoding(char c) { 58 | return c < 0x20 // control chars always have to be encoded 59 | || c == '\"' // chars which must be encoded per JSON spec 60 | || c == '\\' 61 | || c == '\'' // HTML-sensitive chars encoded for safety 62 | || c == '<' 63 | || c == '>' 64 | || (c == '&' && JavaScriptEncodeAmpersand) // Bug Dev11 #133237. Encode '&' to provide additional security for people who incorrectly call the encoding methods (unless turned off by backcompat switch) 65 | || c == '\u0085' // newline chars (see Unicode 6.2, Table 5-1 [http://www.unicode.org/versions/Unicode6.2.0/ch05.pdf]) have to be encoded (DevDiv #663531) 66 | || c == '\u2028' 67 | || c == '\u2029'; 68 | } 69 | 70 | internal static string CollapsePercentUFromStringInternal(string s, Encoding e) { 71 | int count = s.Length; 72 | UrlDecoder helper = new UrlDecoder(count, e); 73 | 74 | // go thorugh the string's chars collapsing just %uXXXX and 75 | // appending each char as char 76 | int loc = s.IndexOf("%u", StringComparison.Ordinal); 77 | if (loc == -1) { 78 | return s; 79 | } 80 | 81 | for (int pos = 0; pos < count; pos++) { 82 | char ch = s[pos]; 83 | 84 | if (ch == '%' && pos < count - 5) { 85 | if (s[pos + 1] == 'u') { 86 | int h1 = HttpEncoderUtility.HexToInt(s[pos + 2]); 87 | int h2 = HttpEncoderUtility.HexToInt(s[pos + 3]); 88 | int h3 = HttpEncoderUtility.HexToInt(s[pos + 4]); 89 | int h4 = HttpEncoderUtility.HexToInt(s[pos + 5]); 90 | 91 | if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { //valid 4 hex chars 92 | ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); 93 | pos += 5; 94 | 95 | // add as char 96 | helper.AddChar(ch); 97 | continue; 98 | } 99 | } 100 | } 101 | if ((ch & 0xFF80) == 0) 102 | helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode 103 | else 104 | helper.AddChar(ch); 105 | } 106 | return Utf16StringValidator.ValidateString(helper.GetString()); 107 | } 108 | 109 | // Encode the header if it contains a CRLF pair 110 | // VSWhidbey 257154 111 | private static string HeaderEncodeInternal(string value) { 112 | string sanitizedHeader = value; 113 | if (HeaderValueNeedsEncoding(value)) { 114 | // DevDiv Bugs 146028 115 | // Denial Of Service scenarios involving 116 | // control characters are possible. 117 | // We are encoding the following characters: 118 | // - All CTL characters except HT (horizontal tab) 119 | // - DEL character (\x7f) 120 | StringBuilder sb = new StringBuilder(); 121 | foreach (char c in value) { 122 | if (c < 32 && c != 9) { 123 | sb.Append(_headerEncodingTable[c]); 124 | } 125 | else if (c == 127) { 126 | sb.Append("%7f"); 127 | } 128 | else { 129 | sb.Append(c); 130 | } 131 | } 132 | sanitizedHeader = sb.ToString(); 133 | } 134 | 135 | return sanitizedHeader; 136 | } 137 | 138 | [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", 139 | Justification = "Input parameter strings are immutable, so this is an appropriate way to return multiple strings.")] 140 | protected internal virtual void HeaderNameValueEncode(string headerName, string headerValue, out string encodedHeaderName, out string encodedHeaderValue) { 141 | encodedHeaderName = (String.IsNullOrEmpty(headerName)) ? headerName : HeaderEncodeInternal(headerName); 142 | encodedHeaderValue = (String.IsNullOrEmpty(headerValue)) ? headerValue : HeaderEncodeInternal(headerValue); 143 | } 144 | 145 | // Returns true if the string contains a control character (other than horizontal tab) or the DEL character. 146 | private static bool HeaderValueNeedsEncoding(string value) { 147 | foreach (char c in value) { 148 | if ((c < 32 && c != 9) || (c == 127)) { 149 | return true; 150 | } 151 | } 152 | return false; 153 | } 154 | 155 | internal string HtmlDecode(string value) { 156 | if (String.IsNullOrEmpty(value)) 157 | { 158 | return value; 159 | } 160 | 161 | if(_isDefaultEncoder) { 162 | return WebUtility.HtmlDecode(value); 163 | } 164 | 165 | StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); 166 | HtmlDecode(value, writer); 167 | return writer.ToString(); 168 | } 169 | 170 | protected internal virtual void HtmlDecode(string value, TextWriter output) { 171 | WebUtility.HtmlDecode(value, output); 172 | } 173 | 174 | internal string HtmlEncode(string value) { 175 | if (String.IsNullOrEmpty(value)) 176 | { 177 | return value; 178 | } 179 | 180 | if(_isDefaultEncoder) { 181 | return WebUtility.HtmlEncode(value); 182 | } 183 | 184 | StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); 185 | HtmlEncode(value, writer); 186 | return writer.ToString(); 187 | } 188 | 189 | protected internal virtual void HtmlEncode(string value, TextWriter output) { 190 | WebUtility.HtmlEncode(value, output); 191 | } 192 | 193 | private static bool IsNonAsciiByte(byte b) { 194 | return (b >= 0x7F || b < 0x20); 195 | } 196 | 197 | protected internal virtual string JavaScriptStringEncode(string value) { 198 | if (String.IsNullOrEmpty(value)) { 199 | return String.Empty; 200 | } 201 | 202 | StringBuilder b = null; 203 | int startIndex = 0; 204 | int count = 0; 205 | for (int i = 0; i < value.Length; i++) { 206 | char c = value[i]; 207 | 208 | // Append the unhandled characters (that do not require special treament) 209 | // to the string builder when special characters are detected. 210 | if (CharRequiresJavaScriptEncoding(c)) { 211 | if (b == null) { 212 | b = new StringBuilder(value.Length + 5); 213 | } 214 | 215 | if (count > 0) { 216 | b.Append(value, startIndex, count); 217 | } 218 | 219 | startIndex = i + 1; 220 | count = 0; 221 | } 222 | 223 | switch (c) { 224 | case '\r': 225 | b.Append("\\r"); 226 | break; 227 | case '\t': 228 | b.Append("\\t"); 229 | break; 230 | case '\"': 231 | b.Append("\\\""); 232 | break; 233 | case '\\': 234 | b.Append("\\\\"); 235 | break; 236 | case '\n': 237 | b.Append("\\n"); 238 | break; 239 | case '\b': 240 | b.Append("\\b"); 241 | break; 242 | case '\f': 243 | b.Append("\\f"); 244 | break; 245 | default: 246 | if (CharRequiresJavaScriptEncoding(c)) { 247 | AppendCharAsUnicodeJavaScript(b, c); 248 | } 249 | else { 250 | count++; 251 | } 252 | break; 253 | } 254 | } 255 | 256 | if (b == null) { 257 | return value; 258 | } 259 | 260 | if (count > 0) { 261 | b.Append(value, startIndex, count); 262 | } 263 | 264 | return b.ToString(); 265 | } 266 | 267 | internal byte[] UrlDecode(byte[] bytes, int offset, int count) { 268 | if (!ValidateUrlEncodingParameters(bytes, offset, count)) { 269 | return null; 270 | } 271 | 272 | int decodedBytesCount = 0; 273 | byte[] decodedBytes = new byte[count]; 274 | 275 | for (int i = 0; i < count; i++) { 276 | int pos = offset + i; 277 | byte b = bytes[pos]; 278 | 279 | if (b == '+') { 280 | b = (byte)' '; 281 | } 282 | else if (b == '%' && i < count - 2) { 283 | int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]); 284 | int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); 285 | 286 | if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars 287 | b = (byte)((h1 << 4) | h2); 288 | i += 2; 289 | } 290 | } 291 | 292 | decodedBytes[decodedBytesCount++] = b; 293 | } 294 | 295 | if (decodedBytesCount < decodedBytes.Length) { 296 | byte[] newDecodedBytes = new byte[decodedBytesCount]; 297 | Array.Copy(decodedBytes, newDecodedBytes, decodedBytesCount); 298 | decodedBytes = newDecodedBytes; 299 | } 300 | 301 | return decodedBytes; 302 | } 303 | 304 | internal string UrlDecode(byte[] bytes, int offset, int count, Encoding encoding) { 305 | if (!ValidateUrlEncodingParameters(bytes, offset, count)) { 306 | return null; 307 | } 308 | 309 | UrlDecoder helper = new UrlDecoder(count, encoding); 310 | 311 | // go through the bytes collapsing %XX and %uXXXX and appending 312 | // each byte as byte, with exception of %uXXXX constructs that 313 | // are appended as chars 314 | 315 | for (int i = 0; i < count; i++) { 316 | int pos = offset + i; 317 | byte b = bytes[pos]; 318 | 319 | // The code assumes that + and % cannot be in multibyte sequence 320 | 321 | if (b == '+') { 322 | b = (byte)' '; 323 | } 324 | else if (b == '%' && i < count - 2) { 325 | if (bytes[pos + 1] == 'u' && i < count - 5) { 326 | int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); 327 | int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 3]); 328 | int h3 = HttpEncoderUtility.HexToInt((char)bytes[pos + 4]); 329 | int h4 = HttpEncoderUtility.HexToInt((char)bytes[pos + 5]); 330 | 331 | if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { // valid 4 hex chars 332 | char ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); 333 | i += 5; 334 | 335 | // don't add as byte 336 | helper.AddChar(ch); 337 | continue; 338 | } 339 | } 340 | else { 341 | int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]); 342 | int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); 343 | 344 | if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars 345 | b = (byte)((h1 << 4) | h2); 346 | i += 2; 347 | } 348 | } 349 | } 350 | 351 | helper.AddByte(b); 352 | } 353 | 354 | return Utf16StringValidator.ValidateString(helper.GetString()); 355 | } 356 | 357 | internal string UrlDecode(string value, Encoding encoding) { 358 | if (value == null) { 359 | return null; 360 | } 361 | 362 | int count = value.Length; 363 | UrlDecoder helper = new UrlDecoder(count, encoding); 364 | 365 | // go through the string's chars collapsing %XX and %uXXXX and 366 | // appending each char as char, with exception of %XX constructs 367 | // that are appended as bytes 368 | 369 | for (int pos = 0; pos < count; pos++) { 370 | char ch = value[pos]; 371 | 372 | if (ch == '+') { 373 | ch = ' '; 374 | } 375 | else if (ch == '%' && pos < count - 2) { 376 | if (value[pos + 1] == 'u' && pos < count - 5) { 377 | int h1 = HttpEncoderUtility.HexToInt(value[pos + 2]); 378 | int h2 = HttpEncoderUtility.HexToInt(value[pos + 3]); 379 | int h3 = HttpEncoderUtility.HexToInt(value[pos + 4]); 380 | int h4 = HttpEncoderUtility.HexToInt(value[pos + 5]); 381 | 382 | if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) { // valid 4 hex chars 383 | ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); 384 | pos += 5; 385 | 386 | // only add as char 387 | helper.AddChar(ch); 388 | continue; 389 | } 390 | } 391 | else { 392 | int h1 = HttpEncoderUtility.HexToInt(value[pos + 1]); 393 | int h2 = HttpEncoderUtility.HexToInt(value[pos + 2]); 394 | 395 | if (h1 >= 0 && h2 >= 0) { // valid 2 hex chars 396 | byte b = (byte)((h1 << 4) | h2); 397 | pos += 2; 398 | 399 | // don't add as char 400 | helper.AddByte(b); 401 | continue; 402 | } 403 | } 404 | } 405 | 406 | if ((ch & 0xFF80) == 0) 407 | helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode 408 | else 409 | helper.AddChar(ch); 410 | } 411 | 412 | return Utf16StringValidator.ValidateString(helper.GetString()); 413 | } 414 | 415 | internal byte[] UrlEncode(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) { 416 | byte[] encoded = UrlEncode(bytes, offset, count); 417 | 418 | return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes)) 419 | ? (byte[])encoded.Clone() 420 | : encoded; 421 | } 422 | 423 | protected internal virtual byte[] UrlEncode(byte[] bytes, int offset, int count) { 424 | if (!ValidateUrlEncodingParameters(bytes, offset, count)) { 425 | return null; 426 | } 427 | 428 | int cSpaces = 0; 429 | int cUnsafe = 0; 430 | 431 | // count them first 432 | for (int i = 0; i < count; i++) { 433 | char ch = (char)bytes[offset + i]; 434 | 435 | if (ch == ' ') 436 | cSpaces++; 437 | else if (!HttpEncoderUtility.IsUrlSafeChar(ch)) 438 | cUnsafe++; 439 | } 440 | 441 | // nothing to expand? 442 | if (cSpaces == 0 && cUnsafe == 0) { 443 | // DevDiv 912606: respect "offset" and "count" 444 | if (0 == offset && bytes.Length == count) { 445 | return bytes; 446 | } 447 | else { 448 | var subarray = new byte[count]; 449 | Buffer.BlockCopy(bytes, offset, subarray, 0, count); 450 | return subarray; 451 | } 452 | } 453 | 454 | // expand not 'safe' characters into %XX, spaces to +s 455 | byte[] expandedBytes = new byte[count + cUnsafe * 2]; 456 | int pos = 0; 457 | 458 | for (int i = 0; i < count; i++) { 459 | byte b = bytes[offset + i]; 460 | char ch = (char)b; 461 | 462 | if (HttpEncoderUtility.IsUrlSafeChar(ch)) { 463 | expandedBytes[pos++] = b; 464 | } 465 | else if (ch == ' ') { 466 | expandedBytes[pos++] = (byte)'+'; 467 | } 468 | else { 469 | expandedBytes[pos++] = (byte)'%'; 470 | expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf); 471 | expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f); 472 | } 473 | } 474 | 475 | return expandedBytes; 476 | } 477 | 478 | // Helper to encode the non-ASCII url characters only 479 | internal String UrlEncodeNonAscii(string str, Encoding e) { 480 | if (String.IsNullOrEmpty(str)) 481 | return str; 482 | if (e == null) 483 | e = Encoding.UTF8; 484 | byte[] bytes = e.GetBytes(str); 485 | byte[] encodedBytes = UrlEncodeNonAscii(bytes, 0, bytes.Length, false /* alwaysCreateNewReturnValue */); 486 | return Encoding.ASCII.GetString(encodedBytes); 487 | } 488 | 489 | internal byte[] UrlEncodeNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateNewReturnValue) { 490 | if (!ValidateUrlEncodingParameters(bytes, offset, count)) { 491 | return null; 492 | } 493 | 494 | int cNonAscii = 0; 495 | 496 | // count them first 497 | for (int i = 0; i < count; i++) { 498 | if (IsNonAsciiByte(bytes[offset + i])) 499 | cNonAscii++; 500 | } 501 | 502 | // nothing to expand? 503 | if (!alwaysCreateNewReturnValue && cNonAscii == 0) 504 | return bytes; 505 | 506 | // expand not 'safe' characters into %XX, spaces to +s 507 | byte[] expandedBytes = new byte[count + cNonAscii * 2]; 508 | int pos = 0; 509 | 510 | for (int i = 0; i < count; i++) { 511 | byte b = bytes[offset + i]; 512 | 513 | if (IsNonAsciiByte(b)) { 514 | expandedBytes[pos++] = (byte)'%'; 515 | expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf); 516 | expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f); 517 | } 518 | else { 519 | expandedBytes[pos++] = b; 520 | } 521 | } 522 | 523 | return expandedBytes; 524 | } 525 | 526 | [Obsolete("This method produces non-standards-compliant output and has interoperability issues. The preferred alternative is UrlEncode(*).")] 527 | internal string UrlEncodeUnicode(string value, bool ignoreAscii) { 528 | if (value == null) { 529 | return null; 530 | } 531 | 532 | int l = value.Length; 533 | StringBuilder sb = new StringBuilder(l); 534 | 535 | for (int i = 0; i < l; i++) { 536 | char ch = value[i]; 537 | 538 | if ((ch & 0xff80) == 0) { // 7 bit? 539 | if (ignoreAscii || HttpEncoderUtility.IsUrlSafeChar(ch)) { 540 | sb.Append(ch); 541 | } 542 | else if (ch == ' ') { 543 | sb.Append('+'); 544 | } 545 | else { 546 | sb.Append('%'); 547 | sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); 548 | sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf)); 549 | } 550 | } 551 | else { // arbitrary Unicode? 552 | sb.Append("%u"); 553 | sb.Append(HttpEncoderUtility.IntToHex((ch >> 12) & 0xf)); 554 | sb.Append(HttpEncoderUtility.IntToHex((ch >> 8) & 0xf)); 555 | sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); 556 | sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf)); 557 | } 558 | } 559 | 560 | return sb.ToString(); 561 | } 562 | 563 | // This is the original UrlPathEncode(string) 564 | [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", 565 | Justification = "Does not represent an entire URL, just a portion.")] 566 | private string UrlPathEncodeImpl(string value) { 567 | if (String.IsNullOrEmpty(value)) { 568 | return value; 569 | } 570 | 571 | // recurse in case there is a query string 572 | int i = value.IndexOf('?'); 573 | if (i >= 0) 574 | return UrlPathEncodeImpl(value.Substring(0, i)) + value.Substring(i); 575 | 576 | // encode DBCS characters and spaces only 577 | return HttpEncoderUtility.UrlEncodeSpaces(UrlEncodeNonAscii(value, Encoding.UTF8)); 578 | } 579 | 580 | internal byte[] UrlTokenDecode(string input, bool lastCharIsPadLength = false) { 581 | if (input == null) 582 | throw new ArgumentNullException("input"); 583 | 584 | if (!lastCharIsPadLength) { 585 | int num = 3 - (input.Length + 3) % 4; 586 | if (num == 0) 587 | { 588 | return new byte[0]; 589 | } 590 | input = input + (char)('0' + num); 591 | } 592 | 593 | int len = input.Length; 594 | if (len < 1) 595 | return new byte[0]; 596 | 597 | /////////////////////////////////////////////////////////////////// 598 | // Step 1: Calculate the number of padding chars to append to this string. 599 | // The number of padding chars to append is stored in the last char of the string. 600 | int numPadChars = (int)input[len - 1] - (int)'0'; 601 | if (numPadChars < 0 || numPadChars > 10) 602 | return null; 603 | 604 | 605 | /////////////////////////////////////////////////////////////////// 606 | // Step 2: Create array to store the chars (not including the last char) 607 | // and the padding chars 608 | char[] base64Chars = new char[len - 1 + numPadChars]; 609 | 610 | 611 | //////////////////////////////////////////////////////// 612 | // Step 3: Copy in the chars. Transform the "-" to "+", and "*" to "/" 613 | for (int iter = 0; iter < len - 1; iter++) { 614 | char c = input[iter]; 615 | 616 | switch (c) { 617 | case '-': 618 | base64Chars[iter] = '+'; 619 | break; 620 | 621 | case '_': 622 | base64Chars[iter] = '/'; 623 | break; 624 | 625 | default: 626 | base64Chars[iter] = c; 627 | break; 628 | } 629 | } 630 | 631 | //////////////////////////////////////////////////////// 632 | // Step 4: Add padding chars 633 | for (int iter = len - 1; iter < base64Chars.Length; iter++) { 634 | base64Chars[iter] = '='; 635 | } 636 | 637 | // Do the actual conversion 638 | return Convert.FromBase64CharArray(base64Chars, 0, base64Chars.Length); 639 | } 640 | 641 | internal string UrlTokenEncode(byte[] input) { 642 | if (input == null) 643 | throw new ArgumentNullException("input"); 644 | if (input.Length < 1) 645 | return String.Empty; 646 | 647 | string base64Str = null; 648 | int endPos = 0; 649 | char[] base64Chars = null; 650 | 651 | //////////////////////////////////////////////////////// 652 | // Step 1: Do a Base64 encoding 653 | base64Str = Convert.ToBase64String(input); 654 | if (base64Str == null) 655 | return null; 656 | 657 | //////////////////////////////////////////////////////// 658 | // Step 2: Find how many padding chars are present in the end 659 | for (endPos = base64Str.Length; endPos > 0; endPos--) { 660 | if (base64Str[endPos - 1] != '=') // Found a non-padding char! 661 | { 662 | break; // Stop here 663 | } 664 | } 665 | 666 | //////////////////////////////////////////////////////// 667 | // Step 3: Create char array to store all non-padding chars, 668 | // plus a char to indicate how many padding chars are needed 669 | base64Chars = new char[endPos + 1]; 670 | base64Chars[endPos] = (char)((int)'0' + base64Str.Length - endPos); // Store a char at the end, to indicate how many padding chars are needed 671 | 672 | //////////////////////////////////////////////////////// 673 | // Step 3: Copy in the other chars. Transform the "+" to "-", and "/" to "_" 674 | for (int iter = 0; iter < endPos; iter++) { 675 | char c = base64Str[iter]; 676 | 677 | switch (c) { 678 | case '+': 679 | base64Chars[iter] = '-'; 680 | break; 681 | 682 | case '/': 683 | base64Chars[iter] = '_'; 684 | break; 685 | 686 | case '=': 687 | Debug.Assert(false); 688 | base64Chars[iter] = c; 689 | break; 690 | 691 | default: 692 | base64Chars[iter] = c; 693 | break; 694 | } 695 | } 696 | return new string(base64Chars); 697 | } 698 | 699 | internal static bool ValidateUrlEncodingParameters(byte[] bytes, int offset, int count) { 700 | if (bytes == null && count == 0) 701 | return false; 702 | if (bytes == null) { 703 | throw new ArgumentNullException("bytes"); 704 | } 705 | if (offset < 0 || offset > bytes.Length) { 706 | throw new ArgumentOutOfRangeException("offset"); 707 | } 708 | if (count < 0 || offset + count > bytes.Length) { 709 | throw new ArgumentOutOfRangeException("count"); 710 | } 711 | 712 | return true; 713 | } 714 | 715 | // Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either chars or bytes 716 | private class UrlDecoder { 717 | private int _bufferSize; 718 | 719 | // Accumulate characters in a special array 720 | private int _numChars; 721 | private char[] _charBuffer; 722 | 723 | // Accumulate bytes for decoding into characters in a special array 724 | private int _numBytes; 725 | private byte[] _byteBuffer; 726 | 727 | // Encoding to convert chars to bytes 728 | private Encoding _encoding; 729 | 730 | private void FlushBytes() { 731 | if (_numBytes > 0) { 732 | _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); 733 | _numBytes = 0; 734 | } 735 | } 736 | 737 | internal UrlDecoder(int bufferSize, Encoding encoding) { 738 | _bufferSize = bufferSize; 739 | _encoding = encoding; 740 | 741 | _charBuffer = new char[bufferSize]; 742 | // byte buffer created on demand 743 | } 744 | 745 | internal void AddChar(char ch) { 746 | if (_numBytes > 0) 747 | FlushBytes(); 748 | 749 | _charBuffer[_numChars++] = ch; 750 | } 751 | 752 | internal void AddByte(byte b) { 753 | // if there are no pending bytes treat 7 bit bytes as characters 754 | // this optimization is temp disable as it doesn't work for some encodings 755 | /* 756 | if (_numBytes == 0 && ((b & 0x80) == 0)) { 757 | AddChar((char)b); 758 | } 759 | else 760 | */ 761 | { 762 | if (_byteBuffer == null) 763 | _byteBuffer = new byte[_bufferSize]; 764 | 765 | _byteBuffer[_numBytes++] = b; 766 | } 767 | } 768 | 769 | internal String GetString() { 770 | if (_numBytes > 0) 771 | FlushBytes(); 772 | 773 | if (_numChars > 0) 774 | return new String(_charBuffer, 0, _numChars); 775 | else 776 | return String.Empty; 777 | } 778 | } 779 | 780 | } 781 | } 782 | -------------------------------------------------------------------------------- /AspNetCrypter/Options.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Options.cs 3 | // 4 | // Authors: 5 | // Jonathan Pryor 6 | // 7 | // Copyright (C) 2008 Novell (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | // Compile With: 30 | // gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll 31 | // gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll 32 | // 33 | // The LINQ version just changes the implementation of 34 | // OptionSet.Parse(IEnumerable), and confers no semantic changes. 35 | 36 | // 37 | // A Getopt::Long-inspired option parsing library for C#. 38 | // 39 | // NDesk.Options.OptionSet is built upon a key/value table, where the 40 | // key is a option format string and the value is a delegate that is 41 | // invoked when the format string is matched. 42 | // 43 | // Option format strings: 44 | // Regex-like BNF Grammar: 45 | // name: .+ 46 | // type: [=:] 47 | // sep: ( [^{}]+ | '{' .+ '}' )? 48 | // aliases: ( name type sep ) ( '|' name type sep )* 49 | // 50 | // Each '|'-delimited name is an alias for the associated action. If the 51 | // format string ends in a '=', it has a required value. If the format 52 | // string ends in a ':', it has an optional value. If neither '=' or ':' 53 | // is present, no value is supported. `=' or `:' need only be defined on one 54 | // alias, but if they are provided on more than one they must be consistent. 55 | // 56 | // Each alias portion may also end with a "key/value separator", which is used 57 | // to split option values if the option accepts > 1 value. If not specified, 58 | // it defaults to '=' and ':'. If specified, it can be any character except 59 | // '{' and '}' OR the *string* between '{' and '}'. If no separator should be 60 | // used (i.e. the separate values should be distinct arguments), then "{}" 61 | // should be used as the separator. 62 | // 63 | // Options are extracted either from the current option by looking for 64 | // the option name followed by an '=' or ':', or is taken from the 65 | // following option IFF: 66 | // - The current option does not contain a '=' or a ':' 67 | // - The current option requires a value (i.e. not a Option type of ':') 68 | // 69 | // The `name' used in the option format string does NOT include any leading 70 | // option indicator, such as '-', '--', or '/'. All three of these are 71 | // permitted/required on any named option. 72 | // 73 | // Option bundling is permitted so long as: 74 | // - '-' is used to start the option group 75 | // - all of the bundled options are a single character 76 | // - at most one of the bundled options accepts a value, and the value 77 | // provided starts from the next character to the end of the string. 78 | // 79 | // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' 80 | // as '-Dname=value'. 81 | // 82 | // Option processing is disabled by specifying "--". All options after "--" 83 | // are returned by OptionSet.Parse() unchanged and unprocessed. 84 | // 85 | // Unprocessed options are returned from OptionSet.Parse(). 86 | // 87 | // Examples: 88 | // int verbose = 0; 89 | // OptionSet p = new OptionSet () 90 | // .Add ("v", v => ++verbose) 91 | // .Add ("name=|value=", v => Console.WriteLine (v)); 92 | // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); 93 | // 94 | // The above would parse the argument string array, and would invoke the 95 | // lambda expression three times, setting `verbose' to 3 when complete. 96 | // It would also print out "A" and "B" to standard output. 97 | // The returned array would contain the string "extra". 98 | // 99 | // C# 3.0 collection initializers are supported and encouraged: 100 | // var p = new OptionSet () { 101 | // { "h|?|help", v => ShowHelp () }, 102 | // }; 103 | // 104 | // System.ComponentModel.TypeConverter is also supported, allowing the use of 105 | // custom data types in the callback type; TypeConverter.ConvertFromString() 106 | // is used to convert the value option to an instance of the specified 107 | // type: 108 | // 109 | // var p = new OptionSet () { 110 | // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, 111 | // }; 112 | // 113 | // Random other tidbits: 114 | // - Boolean options (those w/o '=' or ':' in the option format string) 115 | // are explicitly enabled if they are followed with '+', and explicitly 116 | // disabled if they are followed with '-': 117 | // string a = null; 118 | // var p = new OptionSet () { 119 | // { "a", s => a = s }, 120 | // }; 121 | // p.Parse (new string[]{"-a"}); // sets v != null 122 | // p.Parse (new string[]{"-a+"}); // sets v != null 123 | // p.Parse (new string[]{"-a-"}); // sets v == null 124 | // 125 | 126 | using System; 127 | using System.Collections; 128 | using System.Collections.Generic; 129 | using System.Collections.ObjectModel; 130 | using System.ComponentModel; 131 | using System.Globalization; 132 | using System.IO; 133 | using System.Runtime.Serialization; 134 | using System.Security.Permissions; 135 | using System.Text; 136 | using System.Text.RegularExpressions; 137 | 138 | #if LINQ 139 | using System.Linq; 140 | #endif 141 | 142 | #if TEST 143 | using NDesk.Options; 144 | #endif 145 | 146 | namespace NDesk.Options { 147 | 148 | public class OptionValueCollection : IList, IList { 149 | 150 | List values = new List (); 151 | OptionContext c; 152 | 153 | internal OptionValueCollection (OptionContext c) 154 | { 155 | this.c = c; 156 | } 157 | 158 | #region ICollection 159 | void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} 160 | bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} 161 | object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} 162 | #endregion 163 | 164 | #region ICollection 165 | public void Add (string item) {values.Add (item);} 166 | public void Clear () {values.Clear ();} 167 | public bool Contains (string item) {return values.Contains (item);} 168 | public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} 169 | public bool Remove (string item) {return values.Remove (item);} 170 | public int Count {get {return values.Count;}} 171 | public bool IsReadOnly {get {return false;}} 172 | #endregion 173 | 174 | #region IEnumerable 175 | IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} 176 | #endregion 177 | 178 | #region IEnumerable 179 | public IEnumerator GetEnumerator () {return values.GetEnumerator ();} 180 | #endregion 181 | 182 | #region IList 183 | int IList.Add (object value) {return (values as IList).Add (value);} 184 | bool IList.Contains (object value) {return (values as IList).Contains (value);} 185 | int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} 186 | void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} 187 | void IList.Remove (object value) {(values as IList).Remove (value);} 188 | void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} 189 | bool IList.IsFixedSize {get {return false;}} 190 | object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} 191 | #endregion 192 | 193 | #region IList 194 | public int IndexOf (string item) {return values.IndexOf (item);} 195 | public void Insert (int index, string item) {values.Insert (index, item);} 196 | public void RemoveAt (int index) {values.RemoveAt (index);} 197 | 198 | private void AssertValid (int index) 199 | { 200 | if (c.Option == null) 201 | throw new InvalidOperationException ("OptionContext.Option is null."); 202 | if (index >= c.Option.MaxValueCount) 203 | throw new ArgumentOutOfRangeException ("index"); 204 | if (c.Option.OptionValueType == OptionValueType.Required && 205 | index >= values.Count) 206 | throw new OptionException (string.Format ( 207 | c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), 208 | c.OptionName); 209 | } 210 | 211 | public string this [int index] { 212 | get { 213 | AssertValid (index); 214 | return index >= values.Count ? null : values [index]; 215 | } 216 | set { 217 | values [index] = value; 218 | } 219 | } 220 | #endregion 221 | 222 | public List ToList () 223 | { 224 | return new List (values); 225 | } 226 | 227 | public string[] ToArray () 228 | { 229 | return values.ToArray (); 230 | } 231 | 232 | public override string ToString () 233 | { 234 | return string.Join (", ", values.ToArray ()); 235 | } 236 | } 237 | 238 | public class OptionContext { 239 | private Option option; 240 | private string name; 241 | private int index; 242 | private OptionSet set; 243 | private OptionValueCollection c; 244 | 245 | public OptionContext (OptionSet set) 246 | { 247 | this.set = set; 248 | this.c = new OptionValueCollection (this); 249 | } 250 | 251 | public Option Option { 252 | get {return option;} 253 | set {option = value;} 254 | } 255 | 256 | public string OptionName { 257 | get {return name;} 258 | set {name = value;} 259 | } 260 | 261 | public int OptionIndex { 262 | get {return index;} 263 | set {index = value;} 264 | } 265 | 266 | public OptionSet OptionSet { 267 | get {return set;} 268 | } 269 | 270 | public OptionValueCollection OptionValues { 271 | get {return c;} 272 | } 273 | } 274 | 275 | public enum OptionValueType { 276 | None, 277 | Optional, 278 | Required, 279 | } 280 | 281 | public abstract class Option { 282 | string prototype, description; 283 | string[] names; 284 | OptionValueType type; 285 | int count; 286 | string[] separators; 287 | 288 | protected Option (string prototype, string description) 289 | : this (prototype, description, 1) 290 | { 291 | } 292 | 293 | protected Option (string prototype, string description, int maxValueCount) 294 | { 295 | if (prototype == null) 296 | throw new ArgumentNullException ("prototype"); 297 | if (prototype.Length == 0) 298 | throw new ArgumentException ("Cannot be the empty string.", "prototype"); 299 | if (maxValueCount < 0) 300 | throw new ArgumentOutOfRangeException ("maxValueCount"); 301 | 302 | this.prototype = prototype; 303 | this.names = prototype.Split ('|'); 304 | this.description = description; 305 | this.count = maxValueCount; 306 | this.type = ParsePrototype (); 307 | 308 | if (this.count == 0 && type != OptionValueType.None) 309 | throw new ArgumentException ( 310 | "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + 311 | "OptionValueType.Optional.", 312 | "maxValueCount"); 313 | if (this.type == OptionValueType.None && maxValueCount > 1) 314 | throw new ArgumentException ( 315 | string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), 316 | "maxValueCount"); 317 | if (Array.IndexOf (names, "<>") >= 0 && 318 | ((names.Length == 1 && this.type != OptionValueType.None) || 319 | (names.Length > 1 && this.MaxValueCount > 1))) 320 | throw new ArgumentException ( 321 | "The default option handler '<>' cannot require values.", 322 | "prototype"); 323 | } 324 | 325 | public string Prototype {get {return prototype;}} 326 | public string Description {get {return description;}} 327 | public OptionValueType OptionValueType {get {return type;}} 328 | public int MaxValueCount {get {return count;}} 329 | 330 | public string[] GetNames () 331 | { 332 | return (string[]) names.Clone (); 333 | } 334 | 335 | public string[] GetValueSeparators () 336 | { 337 | if (separators == null) 338 | return new string [0]; 339 | return (string[]) separators.Clone (); 340 | } 341 | 342 | protected static T Parse (string value, OptionContext c) 343 | { 344 | TypeConverter conv = TypeDescriptor.GetConverter (typeof (T)); 345 | T t = default (T); 346 | try { 347 | if (value != null) 348 | t = (T) conv.ConvertFromString (value); 349 | } 350 | catch (Exception e) { 351 | throw new OptionException ( 352 | string.Format ( 353 | c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), 354 | value, typeof (T).Name, c.OptionName), 355 | c.OptionName, e); 356 | } 357 | return t; 358 | } 359 | 360 | internal string[] Names {get {return names;}} 361 | internal string[] ValueSeparators {get {return separators;}} 362 | 363 | static readonly char[] NameTerminator = new char[]{'=', ':'}; 364 | 365 | private OptionValueType ParsePrototype () 366 | { 367 | char type = '\0'; 368 | List seps = new List (); 369 | for (int i = 0; i < names.Length; ++i) { 370 | string name = names [i]; 371 | if (name.Length == 0) 372 | throw new ArgumentException ("Empty option names are not supported.", "prototype"); 373 | 374 | int end = name.IndexOfAny (NameTerminator); 375 | if (end == -1) 376 | continue; 377 | names [i] = name.Substring (0, end); 378 | if (type == '\0' || type == name [end]) 379 | type = name [end]; 380 | else 381 | throw new ArgumentException ( 382 | string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), 383 | "prototype"); 384 | AddSeparators (name, end, seps); 385 | } 386 | 387 | if (type == '\0') 388 | return OptionValueType.None; 389 | 390 | if (count <= 1 && seps.Count != 0) 391 | throw new ArgumentException ( 392 | string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), 393 | "prototype"); 394 | if (count > 1) { 395 | if (seps.Count == 0) 396 | this.separators = new string[]{":", "="}; 397 | else if (seps.Count == 1 && seps [0].Length == 0) 398 | this.separators = null; 399 | else 400 | this.separators = seps.ToArray (); 401 | } 402 | 403 | return type == '=' ? OptionValueType.Required : OptionValueType.Optional; 404 | } 405 | 406 | private static void AddSeparators (string name, int end, ICollection seps) 407 | { 408 | int start = -1; 409 | for (int i = end+1; i < name.Length; ++i) { 410 | switch (name [i]) { 411 | case '{': 412 | if (start != -1) 413 | throw new ArgumentException ( 414 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 415 | "prototype"); 416 | start = i+1; 417 | break; 418 | case '}': 419 | if (start == -1) 420 | throw new ArgumentException ( 421 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 422 | "prototype"); 423 | seps.Add (name.Substring (start, i-start)); 424 | start = -1; 425 | break; 426 | default: 427 | if (start == -1) 428 | seps.Add (name [i].ToString ()); 429 | break; 430 | } 431 | } 432 | if (start != -1) 433 | throw new ArgumentException ( 434 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 435 | "prototype"); 436 | } 437 | 438 | public void Invoke (OptionContext c) 439 | { 440 | OnParseComplete (c); 441 | c.OptionName = null; 442 | c.Option = null; 443 | c.OptionValues.Clear (); 444 | } 445 | 446 | protected abstract void OnParseComplete (OptionContext c); 447 | 448 | public override string ToString () 449 | { 450 | return Prototype; 451 | } 452 | } 453 | 454 | [Serializable] 455 | public class OptionException : Exception { 456 | private string option; 457 | 458 | public OptionException () 459 | { 460 | } 461 | 462 | public OptionException (string message, string optionName) 463 | : base (message) 464 | { 465 | this.option = optionName; 466 | } 467 | 468 | public OptionException (string message, string optionName, Exception innerException) 469 | : base (message, innerException) 470 | { 471 | this.option = optionName; 472 | } 473 | 474 | protected OptionException (SerializationInfo info, StreamingContext context) 475 | : base (info, context) 476 | { 477 | this.option = info.GetString ("OptionName"); 478 | } 479 | 480 | public string OptionName { 481 | get {return this.option;} 482 | } 483 | 484 | [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)] 485 | public override void GetObjectData (SerializationInfo info, StreamingContext context) 486 | { 487 | base.GetObjectData (info, context); 488 | info.AddValue ("OptionName", option); 489 | } 490 | } 491 | 492 | public delegate void OptionAction (TKey key, TValue value); 493 | 494 | public class OptionSet : KeyedCollection 495 | { 496 | public OptionSet () 497 | : this (delegate (string f) {return f;}) 498 | { 499 | } 500 | 501 | public OptionSet (Converter localizer) 502 | { 503 | this.localizer = localizer; 504 | } 505 | 506 | Converter localizer; 507 | 508 | public Converter MessageLocalizer { 509 | get {return localizer;} 510 | } 511 | 512 | protected override string GetKeyForItem (Option item) 513 | { 514 | if (item == null) 515 | throw new ArgumentNullException ("option"); 516 | if (item.Names != null && item.Names.Length > 0) 517 | return item.Names [0]; 518 | // This should never happen, as it's invalid for Option to be 519 | // constructed w/o any names. 520 | throw new InvalidOperationException ("Option has no names!"); 521 | } 522 | 523 | [Obsolete ("Use KeyedCollection.this[string]")] 524 | protected Option GetOptionForName (string option) 525 | { 526 | if (option == null) 527 | throw new ArgumentNullException ("option"); 528 | try { 529 | return base [option]; 530 | } 531 | catch (KeyNotFoundException) { 532 | return null; 533 | } 534 | } 535 | 536 | protected override void InsertItem (int index, Option item) 537 | { 538 | base.InsertItem (index, item); 539 | AddImpl (item); 540 | } 541 | 542 | protected override void RemoveItem (int index) 543 | { 544 | base.RemoveItem (index); 545 | Option p = Items [index]; 546 | // KeyedCollection.RemoveItem() handles the 0th item 547 | for (int i = 1; i < p.Names.Length; ++i) { 548 | Dictionary.Remove (p.Names [i]); 549 | } 550 | } 551 | 552 | protected override void SetItem (int index, Option item) 553 | { 554 | base.SetItem (index, item); 555 | RemoveItem (index); 556 | AddImpl (item); 557 | } 558 | 559 | private void AddImpl (Option option) 560 | { 561 | if (option == null) 562 | throw new ArgumentNullException ("option"); 563 | List added = new List (option.Names.Length); 564 | try { 565 | // KeyedCollection.InsertItem/SetItem handle the 0th name. 566 | for (int i = 1; i < option.Names.Length; ++i) { 567 | Dictionary.Add (option.Names [i], option); 568 | added.Add (option.Names [i]); 569 | } 570 | } 571 | catch (Exception) { 572 | foreach (string name in added) 573 | Dictionary.Remove (name); 574 | throw; 575 | } 576 | } 577 | 578 | public new OptionSet Add (Option option) 579 | { 580 | base.Add (option); 581 | return this; 582 | } 583 | 584 | sealed class ActionOption : Option { 585 | Action action; 586 | 587 | public ActionOption (string prototype, string description, int count, Action action) 588 | : base (prototype, description, count) 589 | { 590 | if (action == null) 591 | throw new ArgumentNullException ("action"); 592 | this.action = action; 593 | } 594 | 595 | protected override void OnParseComplete (OptionContext c) 596 | { 597 | action (c.OptionValues); 598 | } 599 | } 600 | 601 | public OptionSet Add (string prototype, Action action) 602 | { 603 | return Add (prototype, null, action); 604 | } 605 | 606 | public OptionSet Add (string prototype, string description, Action action) 607 | { 608 | if (action == null) 609 | throw new ArgumentNullException ("action"); 610 | Option p = new ActionOption (prototype, description, 1, 611 | delegate (OptionValueCollection v) { action (v [0]); }); 612 | base.Add (p); 613 | return this; 614 | } 615 | 616 | public OptionSet Add (string prototype, OptionAction action) 617 | { 618 | return Add (prototype, null, action); 619 | } 620 | 621 | public OptionSet Add (string prototype, string description, OptionAction action) 622 | { 623 | if (action == null) 624 | throw new ArgumentNullException ("action"); 625 | Option p = new ActionOption (prototype, description, 2, 626 | delegate (OptionValueCollection v) {action (v [0], v [1]);}); 627 | base.Add (p); 628 | return this; 629 | } 630 | 631 | sealed class ActionOption : Option { 632 | Action action; 633 | 634 | public ActionOption (string prototype, string description, Action action) 635 | : base (prototype, description, 1) 636 | { 637 | if (action == null) 638 | throw new ArgumentNullException ("action"); 639 | this.action = action; 640 | } 641 | 642 | protected override void OnParseComplete (OptionContext c) 643 | { 644 | action (Parse (c.OptionValues [0], c)); 645 | } 646 | } 647 | 648 | sealed class ActionOption : Option { 649 | OptionAction action; 650 | 651 | public ActionOption (string prototype, string description, OptionAction action) 652 | : base (prototype, description, 2) 653 | { 654 | if (action == null) 655 | throw new ArgumentNullException ("action"); 656 | this.action = action; 657 | } 658 | 659 | protected override void OnParseComplete (OptionContext c) 660 | { 661 | action ( 662 | Parse (c.OptionValues [0], c), 663 | Parse (c.OptionValues [1], c)); 664 | } 665 | } 666 | 667 | public OptionSet Add (string prototype, Action action) 668 | { 669 | return Add (prototype, null, action); 670 | } 671 | 672 | public OptionSet Add (string prototype, string description, Action action) 673 | { 674 | return Add (new ActionOption (prototype, description, action)); 675 | } 676 | 677 | public OptionSet Add (string prototype, OptionAction action) 678 | { 679 | return Add (prototype, null, action); 680 | } 681 | 682 | public OptionSet Add (string prototype, string description, OptionAction action) 683 | { 684 | return Add (new ActionOption (prototype, description, action)); 685 | } 686 | 687 | protected virtual OptionContext CreateOptionContext () 688 | { 689 | return new OptionContext (this); 690 | } 691 | 692 | #if LINQ 693 | public List Parse (IEnumerable arguments) 694 | { 695 | bool process = true; 696 | OptionContext c = CreateOptionContext (); 697 | c.OptionIndex = -1; 698 | var def = GetOptionForName ("<>"); 699 | var unprocessed = 700 | from argument in arguments 701 | where ++c.OptionIndex >= 0 && (process || def != null) 702 | ? process 703 | ? argument == "--" 704 | ? (process = false) 705 | : !Parse (argument, c) 706 | ? def != null 707 | ? Unprocessed (null, def, c, argument) 708 | : true 709 | : false 710 | : def != null 711 | ? Unprocessed (null, def, c, argument) 712 | : true 713 | : true 714 | select argument; 715 | List r = unprocessed.ToList (); 716 | if (c.Option != null) 717 | c.Option.Invoke (c); 718 | return r; 719 | } 720 | #else 721 | public List Parse (IEnumerable arguments) 722 | { 723 | OptionContext c = CreateOptionContext (); 724 | c.OptionIndex = -1; 725 | bool process = true; 726 | List unprocessed = new List (); 727 | Option def = Contains ("<>") ? this ["<>"] : null; 728 | foreach (string argument in arguments) { 729 | ++c.OptionIndex; 730 | if (argument == "--") { 731 | process = false; 732 | continue; 733 | } 734 | if (!process) { 735 | Unprocessed (unprocessed, def, c, argument); 736 | continue; 737 | } 738 | if (!Parse (argument, c)) 739 | Unprocessed (unprocessed, def, c, argument); 740 | } 741 | if (c.Option != null) 742 | c.Option.Invoke (c); 743 | return unprocessed; 744 | } 745 | #endif 746 | 747 | private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) 748 | { 749 | if (def == null) { 750 | extra.Add (argument); 751 | return false; 752 | } 753 | c.OptionValues.Add (argument); 754 | c.Option = def; 755 | c.Option.Invoke (c); 756 | return false; 757 | } 758 | 759 | private readonly Regex ValueOption = new Regex ( 760 | @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); 761 | 762 | protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) 763 | { 764 | if (argument == null) 765 | throw new ArgumentNullException ("argument"); 766 | 767 | flag = name = sep = value = null; 768 | Match m = ValueOption.Match (argument); 769 | if (!m.Success) { 770 | return false; 771 | } 772 | flag = m.Groups ["flag"].Value; 773 | name = m.Groups ["name"].Value; 774 | if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { 775 | sep = m.Groups ["sep"].Value; 776 | value = m.Groups ["value"].Value; 777 | } 778 | return true; 779 | } 780 | 781 | protected virtual bool Parse (string argument, OptionContext c) 782 | { 783 | if (c.Option != null) { 784 | ParseValue (argument, c); 785 | return true; 786 | } 787 | 788 | string f, n, s, v; 789 | if (!GetOptionParts (argument, out f, out n, out s, out v)) 790 | return false; 791 | 792 | Option p; 793 | if (Contains (n)) { 794 | p = this [n]; 795 | c.OptionName = f + n; 796 | c.Option = p; 797 | switch (p.OptionValueType) { 798 | case OptionValueType.None: 799 | c.OptionValues.Add (n); 800 | c.Option.Invoke (c); 801 | break; 802 | case OptionValueType.Optional: 803 | case OptionValueType.Required: 804 | ParseValue (v, c); 805 | break; 806 | } 807 | return true; 808 | } 809 | // no match; is it a bool option? 810 | if (ParseBool (argument, n, c)) 811 | return true; 812 | // is it a bundled option? 813 | if (ParseBundledValue (f, string.Concat (n + s + v), c)) 814 | return true; 815 | 816 | return false; 817 | } 818 | 819 | private void ParseValue (string option, OptionContext c) 820 | { 821 | if (option != null) 822 | foreach (string o in c.Option.ValueSeparators != null 823 | ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None) 824 | : new string[]{option}) { 825 | c.OptionValues.Add (o); 826 | } 827 | if (c.OptionValues.Count == c.Option.MaxValueCount || 828 | c.Option.OptionValueType == OptionValueType.Optional) 829 | c.Option.Invoke (c); 830 | else if (c.OptionValues.Count > c.Option.MaxValueCount) { 831 | throw new OptionException (localizer (string.Format ( 832 | "Error: Found {0} option values when expecting {1}.", 833 | c.OptionValues.Count, c.Option.MaxValueCount)), 834 | c.OptionName); 835 | } 836 | } 837 | 838 | private bool ParseBool (string option, string n, OptionContext c) 839 | { 840 | Option p; 841 | string rn; 842 | if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && 843 | Contains ((rn = n.Substring (0, n.Length-1)))) { 844 | p = this [rn]; 845 | string v = n [n.Length-1] == '+' ? option : null; 846 | c.OptionName = option; 847 | c.Option = p; 848 | c.OptionValues.Add (v); 849 | p.Invoke (c); 850 | return true; 851 | } 852 | return false; 853 | } 854 | 855 | private bool ParseBundledValue (string f, string n, OptionContext c) 856 | { 857 | if (f != "-") 858 | return false; 859 | for (int i = 0; i < n.Length; ++i) { 860 | Option p; 861 | string opt = f + n [i].ToString (); 862 | string rn = n [i].ToString (); 863 | if (!Contains (rn)) { 864 | if (i == 0) 865 | return false; 866 | throw new OptionException (string.Format (localizer ( 867 | "Cannot bundle unregistered option '{0}'."), opt), opt); 868 | } 869 | p = this [rn]; 870 | switch (p.OptionValueType) { 871 | case OptionValueType.None: 872 | Invoke (c, opt, n, p); 873 | break; 874 | case OptionValueType.Optional: 875 | case OptionValueType.Required: { 876 | string v = n.Substring (i+1); 877 | c.Option = p; 878 | c.OptionName = opt; 879 | ParseValue (v.Length != 0 ? v : null, c); 880 | return true; 881 | } 882 | default: 883 | throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); 884 | } 885 | } 886 | return true; 887 | } 888 | 889 | private static void Invoke (OptionContext c, string name, string value, Option option) 890 | { 891 | c.OptionName = name; 892 | c.Option = option; 893 | c.OptionValues.Add (value); 894 | option.Invoke (c); 895 | } 896 | 897 | private const int OptionWidth = 29; 898 | 899 | public void WriteOptionDescriptions (TextWriter o) 900 | { 901 | foreach (Option p in this) { 902 | int written = 0; 903 | if (!WriteOptionPrototype (o, p, ref written)) 904 | continue; 905 | 906 | if (written < OptionWidth) 907 | o.Write (new string (' ', OptionWidth - written)); 908 | else { 909 | o.WriteLine (); 910 | o.Write (new string (' ', OptionWidth)); 911 | } 912 | 913 | List lines = GetLines (localizer (GetDescription (p.Description))); 914 | o.WriteLine (lines [0]); 915 | string prefix = new string (' ', OptionWidth+2); 916 | for (int i = 1; i < lines.Count; ++i) { 917 | o.Write (prefix); 918 | o.WriteLine (lines [i]); 919 | } 920 | } 921 | } 922 | 923 | bool WriteOptionPrototype (TextWriter o, Option p, ref int written) 924 | { 925 | string[] names = p.Names; 926 | 927 | int i = GetNextOptionIndex (names, 0); 928 | if (i == names.Length) 929 | return false; 930 | 931 | if (names [i].Length == 1) { 932 | Write (o, ref written, " -"); 933 | Write (o, ref written, names [0]); 934 | } 935 | else { 936 | Write (o, ref written, " --"); 937 | Write (o, ref written, names [0]); 938 | } 939 | 940 | for ( i = GetNextOptionIndex (names, i+1); 941 | i < names.Length; i = GetNextOptionIndex (names, i+1)) { 942 | Write (o, ref written, ", "); 943 | Write (o, ref written, names [i].Length == 1 ? "-" : "--"); 944 | Write (o, ref written, names [i]); 945 | } 946 | 947 | if (p.OptionValueType == OptionValueType.Optional || 948 | p.OptionValueType == OptionValueType.Required) { 949 | if (p.OptionValueType == OptionValueType.Optional) { 950 | Write (o, ref written, localizer ("[")); 951 | } 952 | Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); 953 | string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 954 | ? p.ValueSeparators [0] 955 | : " "; 956 | for (int c = 1; c < p.MaxValueCount; ++c) { 957 | Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); 958 | } 959 | if (p.OptionValueType == OptionValueType.Optional) { 960 | Write (o, ref written, localizer ("]")); 961 | } 962 | } 963 | return true; 964 | } 965 | 966 | static int GetNextOptionIndex (string[] names, int i) 967 | { 968 | while (i < names.Length && names [i] == "<>") { 969 | ++i; 970 | } 971 | return i; 972 | } 973 | 974 | static void Write (TextWriter o, ref int n, string s) 975 | { 976 | n += s.Length; 977 | o.Write (s); 978 | } 979 | 980 | private static string GetArgumentName (int index, int maxIndex, string description) 981 | { 982 | if (description == null) 983 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 984 | string[] nameStart; 985 | if (maxIndex == 1) 986 | nameStart = new string[]{"{0:", "{"}; 987 | else 988 | nameStart = new string[]{"{" + index + ":"}; 989 | for (int i = 0; i < nameStart.Length; ++i) { 990 | int start, j = 0; 991 | do { 992 | start = description.IndexOf (nameStart [i], j); 993 | } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false); 994 | if (start == -1) 995 | continue; 996 | int end = description.IndexOf ("}", start); 997 | if (end == -1) 998 | continue; 999 | return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length); 1000 | } 1001 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1002 | } 1003 | 1004 | private static string GetDescription (string description) 1005 | { 1006 | if (description == null) 1007 | return string.Empty; 1008 | StringBuilder sb = new StringBuilder (description.Length); 1009 | int start = -1; 1010 | for (int i = 0; i < description.Length; ++i) { 1011 | switch (description [i]) { 1012 | case '{': 1013 | if (i == start) { 1014 | sb.Append ('{'); 1015 | start = -1; 1016 | } 1017 | else if (start < 0) 1018 | start = i + 1; 1019 | break; 1020 | case '}': 1021 | if (start < 0) { 1022 | if ((i+1) == description.Length || description [i+1] != '}') 1023 | throw new InvalidOperationException ("Invalid option description: " + description); 1024 | ++i; 1025 | sb.Append ("}"); 1026 | } 1027 | else { 1028 | sb.Append (description.Substring (start, i - start)); 1029 | start = -1; 1030 | } 1031 | break; 1032 | case ':': 1033 | if (start < 0) 1034 | goto default; 1035 | start = i + 1; 1036 | break; 1037 | default: 1038 | if (start < 0) 1039 | sb.Append (description [i]); 1040 | break; 1041 | } 1042 | } 1043 | return sb.ToString (); 1044 | } 1045 | 1046 | private static List GetLines (string description) 1047 | { 1048 | List lines = new List (); 1049 | if (string.IsNullOrEmpty (description)) { 1050 | lines.Add (string.Empty); 1051 | return lines; 1052 | } 1053 | int length = 80 - OptionWidth - 2; 1054 | int start = 0, end; 1055 | do { 1056 | end = GetLineEnd (start, length, description); 1057 | bool cont = false; 1058 | if (end < description.Length) { 1059 | char c = description [end]; 1060 | if (c == '-' || (char.IsWhiteSpace (c) && c != '\n')) 1061 | ++end; 1062 | else if (c != '\n') { 1063 | cont = true; 1064 | --end; 1065 | } 1066 | } 1067 | lines.Add (description.Substring (start, end - start)); 1068 | if (cont) { 1069 | lines [lines.Count-1] += "-"; 1070 | } 1071 | start = end; 1072 | if (start < description.Length && description [start] == '\n') 1073 | ++start; 1074 | } while (end < description.Length); 1075 | return lines; 1076 | } 1077 | 1078 | private static int GetLineEnd (int start, int length, string description) 1079 | { 1080 | int end = Math.Min (start + length, description.Length); 1081 | int sep = -1; 1082 | for (int i = start; i < end; ++i) { 1083 | switch (description [i]) { 1084 | case ' ': 1085 | case '\t': 1086 | case '\v': 1087 | case '-': 1088 | case ',': 1089 | case '.': 1090 | case ';': 1091 | sep = i; 1092 | break; 1093 | case '\n': 1094 | return i; 1095 | } 1096 | } 1097 | if (sep == -1 || end == description.Length) 1098 | return end; 1099 | return sep; 1100 | } 1101 | } 1102 | } 1103 | 1104 | --------------------------------------------------------------------------------