File: MachineKey.cs

package info (click to toggle)
mono 6.8.0.105%2Bdfsg-3.3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,284,512 kB
  • sloc: cs: 11,172,132; xml: 2,850,069; ansic: 671,653; cpp: 122,091; perl: 59,366; javascript: 30,841; asm: 22,168; makefile: 20,093; sh: 15,020; python: 4,827; pascal: 925; sql: 859; sed: 16; php: 1
file content (215 lines) | stat: -rw-r--r-- 12,288 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//------------------------------------------------------------------------------
// <copyright file="MachineKey.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

/*
 * MachineKey
 *
 * Copyright (c) 2009 Microsoft Corporation
 */

namespace System.Web.Security {
    using System;
    using System.Linq;
    using System.Web.Configuration;
    using System.Web.Security.Cryptography;
    using System.Web.Util;

    /////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////
    public enum MachineKeyProtection {
        All,
        Encryption,
        Validation
    }

    /////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////
    public static class MachineKey {
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        [Obsolete("This method is obsolete and is only provided for compatibility with existing code. It is recommended that new code use the Protect and Unprotect methods instead.")]
        public static string Encode(byte[] data, MachineKeyProtection protectionOption) {
            if (data == null)
                throw new ArgumentNullException("data");

            //////////////////////////////////////////////////////////////////////
            // Step 1: Get the MAC and add to the blob
            if (protectionOption == MachineKeyProtection.All || protectionOption == MachineKeyProtection.Validation) {
                byte[] bHash = MachineKeySection.HashData(data, null, 0, data.Length);
                byte[] bAll = new byte[bHash.Length + data.Length];
                Buffer.BlockCopy(data, 0, bAll, 0, data.Length);
                Buffer.BlockCopy(bHash, 0, bAll, data.Length, bHash.Length);
                data = bAll;
            }

            if (protectionOption == MachineKeyProtection.All || protectionOption == MachineKeyProtection.Encryption) {
                //////////////////////////////////////////////////////////////////////
                // Step 2: Encryption
                data = MachineKeySection.EncryptOrDecryptData(true, data, null, 0, data.Length, false, false, IVType.Random, !AppSettings.UseLegacyMachineKeyEncryption);
            }

            //////////////////////////////////////////////////////////////////////
            // Step 3: Covert the buffer to HEX string and return it
            return CryptoUtil.BinaryToHex(data);
        }

        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        [Obsolete("This method is obsolete and is only provided for compatibility with existing code. It is recommended that new code use the Protect and Unprotect methods instead.")]
        public static byte[] Decode(string encodedData, MachineKeyProtection protectionOption) {
            if (encodedData == null)
                throw new ArgumentNullException("encodedData");

            if ((encodedData.Length % 2) != 0)
                throw new ArgumentException(null, "encodedData");

            byte[] data = null;
            try {
                //////////////////////////////////////////////////////////////////////
                // Step 1: Covert the HEX string to byte array
                data = CryptoUtil.HexToBinary(encodedData);
            }
            catch {
                throw new ArgumentException(null, "encodedData");
            }

            if (data == null || data.Length < 1)
                throw new ArgumentException(null, "encodedData");

            if (protectionOption == MachineKeyProtection.All || protectionOption == MachineKeyProtection.Encryption) {
                //////////////////////////////////////////////////////////////////
                // Step 2: Decrypt the data
                data = MachineKeySection.EncryptOrDecryptData(false, data, null, 0, data.Length, false, false, IVType.Random, !AppSettings.UseLegacyMachineKeyEncryption);
                if (data == null)
                    return null;
            }

            if (protectionOption == MachineKeyProtection.All || protectionOption == MachineKeyProtection.Validation) {
                //////////////////////////////////////////////////////////////////
                // Step 3a: Remove the hash from the end of the data
                if (data.Length < MachineKeySection.HashSize)
                    return null;
                byte[] originalData = data;
                data = new byte[originalData.Length - MachineKeySection.HashSize];
                Buffer.BlockCopy(originalData, 0, data, 0, data.Length);

                //////////////////////////////////////////////////////////////////
                // Step 3b: Calculate the hash and make sure it matches
                byte[] bHash = MachineKeySection.HashData(data, null, 0, data.Length);
                if (bHash == null || bHash.Length != MachineKeySection.HashSize)
                    return null; // Sizes don't match
                for (int iter = 0; iter < bHash.Length; iter++) {
                    if (bHash[iter] != originalData[data.Length + iter])
                        return null; // Mis-match found
                }
            }

            return data;
        }

        /// <summary>
        /// Cryptographically protects and tamper-proofs the specified data.
        /// </summary>
        /// <param name="userData">The plaintext data that needs to be protected.</param>
        /// <param name="purposes">(optional) A list of purposes that describe what the data is meant for.
        /// If this value is specified, the same list must be passed to the Unprotect method in order
        /// to decipher the returned ciphertext.</param>
        /// <returns>The ciphertext data. To decipher the data, call the Unprotect method, passing this
        /// value as the 'protectedData' parameter.</returns>
        /// <remarks>
        /// This method supercedes the Encode method, which required the caller to know whether he wanted
        /// the plaintext data to be encrypted, signed, or both. In contrast, the Protect method just
        /// does the right thing and securely protects the data. Ciphertext data produced by this method
        /// can only be deciphered by the Unprotect method.
        /// 
        /// The 'purposes' parameter is an optional list of reason strings that can lock the ciphertext
        /// to a specific purpose. The intent of this parameter is that different subsystems within
        /// an application may depend on cryptographic operations, and a malicious client should not be
        /// able to get the result of one subsystem's Protect method and feed it as input to another
        /// subsystem's Unprotect method, which could have undesirable or insecure behavior. In essence,
        /// the 'purposes' parameter helps ensure that some protected data can be consumed only by the
        /// component that originally generated it. Applications should take care to ensure that each
        /// subsystem uses a unique 'purposes' list.
        ///
        /// For example, to protect or unprotect an authentication token, the application could call:
        /// MachineKey.Protect(..., "Authentication token");
        /// MachineKey.Unprotect(..., "Authentication token");
        /// 
        /// Applications may dynamically generate the 'purposes' parameter if desired. If an application
        /// does this, user-supplied values like usernames should never directly be passed for the 'purposes'
        /// parameter. They should instead be prefixed with something (like "Username: " + username) to
        /// minimize the risk of a malicious client crafting input that collides with a token in use by some
        /// other part of the system. Any dynamically-generated tokens should come after non-dynamically
        /// generated tokens.
        /// 
        /// For example, to protect or unprotect a private message that is tied to a specific user, the
        /// application could call:
        /// MachineKey.Protect(..., "Private message", "Recipient: " + username);
        /// MachineKey.Unprotect(..., "Private message", "Recipient: " + username);
        /// 
        /// In both of the above examples, is it important that the caller of the Unprotect method be able to
        /// resurrect the original 'purposes' list. Otherwise the operation will fail with a CryptographicException.
        /// </remarks>
        public static byte[] Protect(byte[] userData, params string[] purposes) {
            if (userData == null) {
                throw new ArgumentNullException("userData");
            }

            // Technically we don't care if the purposes array contains whitespace-only entries,
            // but the DataProtector class does, so we'll just block them right here.
            if (purposes != null && purposes.Any(String.IsNullOrWhiteSpace)) {
                throw new ArgumentException(SR.GetString(SR.MachineKey_InvalidPurpose), "purposes");
            }

            return Protect(AspNetCryptoServiceProvider.Instance, userData, purposes);
        }

        // Internal method for unit testing.
        internal static byte[] Protect(ICryptoServiceProvider cryptoServiceProvider, byte[] userData, string[] purposes) {
            // If the user is calling this method, we want to use the ICryptoServiceProvider
            // regardless of whether or not it's the default provider.

            Purpose derivedPurpose = Purpose.User_MachineKey_Protect.AppendSpecificPurposes(purposes);
            ICryptoService cryptoService = cryptoServiceProvider.GetCryptoService(derivedPurpose);
            return cryptoService.Protect(userData);
        }

        /// <summary>
        /// Verifies the integrity of and deciphers the given ciphertext.
        /// </summary>
        /// <param name="protectedData">Ciphertext data that was produced by the Protect method.</param>
        /// <param name="purposes">(optional) A list of purposes that describe what the data is meant for.</param>
        /// <returns>The plaintext data.</returns>
        /// <exception>Throws a CryptographicException if decryption fails. This can occur if the 'protectedData' has
        /// been tampered with, if an incorrect 'purposes' parameter is specified, or if an application is deployed
        /// to more than one server (as in a farm scenario) but is using auto-generated encryption keys.</exception>
        /// <remarks>See documentation on the Protect method for more information.</remarks>
        public static byte[] Unprotect(byte[] protectedData, params string[] purposes) {
            if (protectedData == null) {
                throw new ArgumentNullException("protectedData");
            }

            // Technically we don't care if the purposes array contains whitespace-only entries,
            // but the DataProtector class does, so we'll just block them right here.
            if (purposes != null && purposes.Any(String.IsNullOrWhiteSpace)) {
                throw new ArgumentException(SR.GetString(SR.MachineKey_InvalidPurpose), "purposes");
            }

            return Unprotect(AspNetCryptoServiceProvider.Instance, protectedData, purposes);
        }

        // Internal method for unit testing.
        internal static byte[] Unprotect(ICryptoServiceProvider cryptoServiceProvider, byte[] protectedData, string[] purposes) {
            // If the user is calling this method, we want to use the ICryptoServiceProvider
            // regardless of whether or not it's the default provider.

            Purpose derivedPurpose = Purpose.User_MachineKey_Protect.AppendSpecificPurposes(purposes);
            ICryptoService cryptoService = cryptoServiceProvider.GetCryptoService(derivedPurpose);
            return cryptoService.Unprotect(protectedData);
        }

    }
}