├── 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 |
--------------------------------------------------------------------------------