Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.IdentityModel / System / IdentityModel / RsaEncryptionCookieTransform.cs
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 using System.Collections.Generic;
5 using System.Collections.ObjectModel;
6 using System.IO;
7 using System.Security.Cryptography;
8 using System.Security.Cryptography.X509Certificates;
9 using System.Text;
10
11 namespace System.IdentityModel
12 {
13     /// <summary>
14     /// Encrypts a cookie using <see cref="RSA"/>.
15     /// </summary>
16     /// <remarks>
17     /// <para>
18     /// Cookies encrypted with this transform may be decrypted 
19     /// by any machine that shares the same RSA private key (generally 
20     /// associated with an X509 certificate).
21     /// </para>
22     /// <para>
23     /// The given data is encrypted using a random AES256 key.  This key is
24     /// then encrypted using RSA, and the RSA public key is sent in plain text
25     /// so that when decoding the class knows which RSA key to use.
26     /// </para>
27     /// </remarks>
28     public class RsaEncryptionCookieTransform : CookieTransform
29     {
30         //
31         // Produces an encrypted stream as follows:
32         // 
33         // Hashsha?( RSA.ToString( false ) ) +
34         // Length( EncryptRSA( Key + IV )    +
35         // EncryptRSA( Key + IV )            +
36         // Length( EncryptAES( Data )        +
37         // EncryptAES( Data )
38         // 
39
40         RSA _encryptionKey;
41         List<RSA> _decryptionKeys = new List<RSA>();
42         string _hashName = "SHA256";
43
44         /// <summary>
45         /// Creates a new instance of <see cref="RsaEncryptionCookieTransform"/>.
46         /// </summary>
47         /// <param name="key">The provided key will be used as the encryption and decryption key by default.</param>
48         /// <exception cref="ArgumentNullException">When the key is null.</exception>
49         public RsaEncryptionCookieTransform(RSA key)
50         {
51             if (null == key)
52             {
53                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("key");
54             }
55             _encryptionKey = key;
56             _decryptionKeys.Add(_encryptionKey);
57         }
58
59         /// <summary>
60         /// Creates a new instance of <see cref="RsaEncryptionCookieTransform"/>
61         /// </summary>
62         /// <param name="certificate">Certificate whose private key is used to encrypt and decrypt.</param>
63         /// <exception cref="ArgumentNullException">When certificate is null.</exception>
64         /// <exception cref="ArgumentException">When the certificate has no private key.</exception>
65         /// <exception cref="ArgumentException">When the certificate's key is not RSA.</exception>
66         public RsaEncryptionCookieTransform(X509Certificate2 certificate)
67         {
68             if (null == certificate)
69             {
70                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
71             }
72             _encryptionKey = X509Util.EnsureAndGetPrivateRSAKey(certificate);
73             _decryptionKeys.Add(_encryptionKey);
74         }
75
76         /// <summary>
77         /// Creates a new instance of <see cref="RsaEncryptionCookieTransform"/>.
78         /// The instance created by this constructor is not usable until the signing and verification keys are set.
79         /// </summary>
80         internal RsaEncryptionCookieTransform()
81         {
82         }
83
84         /// <summary>
85         /// Gets or sets the RSA key used for encryption
86         /// </summary>
87         public virtual RSA EncryptionKey
88         {
89             get { return _encryptionKey; }
90             set
91             {
92                 _encryptionKey = value;
93                 _decryptionKeys = new List<RSA>(new RSA[] { _encryptionKey });
94             }
95         }
96
97         /// <summary>
98         /// Gets the keys used for decryption
99         /// By default, this property returns a list containing only the encryption key.
100         /// </summary>
101         protected virtual ReadOnlyCollection<RSA> DecryptionKeys
102         {
103             get
104             {
105                 return _decryptionKeys.AsReadOnly();
106             }
107         }
108
109         /// <summary>
110         /// Gets or sets the name of the hash algorithm to use.
111         /// </summary>
112         /// <remarks>
113         /// SHA256 is the default algorithm. This may require a minimum platform of Windows Server 2003 and .NET 3.5 SP1.
114         /// If SHA256 is not supported, set HashName to "SHA1".
115         /// </remarks>
116         public string HashName
117         {
118             get { return _hashName; }
119             set
120             {
121                 using (HashAlgorithm algorithm = CryptoHelper.CreateHashAlgorithm(value))
122                 {
123                     if (algorithm == null)
124                     {
125                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.ID6034, value));
126                     }
127                     _hashName = value;
128                 }
129             }
130         }
131
132         /// <summary>
133         /// Decrypts data using the provided RSA key(s) to decrypt an AES key, which decrypts the cookie.
134         /// </summary>
135         /// <param name="encoded">The encoded data</param>
136         /// <returns>The decoded data</returns>
137         /// <exception cref="ArgumentNullException">The argument 'encoded' is null.</exception>
138         /// <exception cref="ArgumentException">The argument 'encoded' contains zero bytes.</exception>
139         /// <exception cref="NotSupportedException">The platform does not support the requested algorithm.</exception>
140         /// <exception cref="InvalidOperationException">There are no decryption keys or none of the keys match.</exception>
141         public override byte[] Decode(byte[] encoded)
142         {
143             if (null == encoded)
144             {
145                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("encoded");
146             }
147
148             if (0 == encoded.Length)
149             {
150                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("encoded", SR.GetString(SR.ID6045));
151             }
152
153             ReadOnlyCollection<RSA> decryptionKeys = DecryptionKeys;
154
155             if (0 == decryptionKeys.Count)
156             {
157                 throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6039));
158             }
159
160             byte[] encryptedKeyAndIV;
161             byte[] encryptedData;
162             byte[] rsaHash;
163             RSA rsaDecryptionKey = null;
164
165             using (HashAlgorithm hash = CryptoHelper.CreateHashAlgorithm(_hashName))
166             {
167                 int hashSizeInBytes = hash.HashSize / 8;
168                 using (BinaryReader br = new BinaryReader(new MemoryStream(encoded)))
169                 {
170                     rsaHash = br.ReadBytes(hashSizeInBytes);
171                     int encryptedKeyAndIVSize = br.ReadInt32();
172                     if (encryptedKeyAndIVSize < 0)
173                     {
174                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1006, encryptedKeyAndIVSize)));
175                     }
176                     //
177                     // Enforce upper limit on key size to prevent large buffer allocation in br.ReadBytes()
178                     //
179
180                     if (encryptedKeyAndIVSize > encoded.Length)
181                     {
182                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1007)));
183                     }
184                     encryptedKeyAndIV = br.ReadBytes(encryptedKeyAndIVSize);
185
186                     int encryptedDataSize = br.ReadInt32();
187                     if (encryptedDataSize < 0)
188                     {
189                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1008, encryptedDataSize)));
190                     }
191                     //
192                     // Enforce upper limit on data size to prevent large buffer allocation in br.ReadBytes()
193                     //
194                     if (encryptedDataSize > encoded.Length)
195                     {
196                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1009)));
197                     }
198
199                     encryptedData = br.ReadBytes(encryptedDataSize);
200                 }
201
202                 //
203                 // Find the decryption key matching the one in XML
204                 //
205                 foreach (RSA key in decryptionKeys)
206                 {
207                     byte[] hashedKey = hash.ComputeHash(Encoding.UTF8.GetBytes(key.ToXmlString(false)));
208                     if (CryptoHelper.IsEqual(hashedKey, rsaHash))
209                     {
210                         rsaDecryptionKey = key;
211                         break;
212                     }
213                 }
214             }
215
216             if (rsaDecryptionKey == null)
217             {
218                 throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6040));
219             }
220
221             byte[] decryptedKeyAndIV = CngLightup.OaepSha1Decrypt(rsaDecryptionKey, encryptedKeyAndIV);
222
223             using (SymmetricAlgorithm symmetricAlgorithm = CryptoHelper.NewDefaultEncryption())
224             {
225
226                 byte[] decryptionKey = new byte[symmetricAlgorithm.KeySize / 8];
227
228                 //
229                 // Ensure there is sufficient length in the descrypted key and IV buffer for an IV.
230                 //
231                 if (decryptedKeyAndIV.Length < decryptionKey.Length)
232                 {
233                     throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6047, decryptedKeyAndIV.Length, decryptionKey.Length));
234                 }
235
236                 byte[] decryptionIV = new byte[decryptedKeyAndIV.Length - decryptionKey.Length];
237
238                 //
239                 // Copy key into its own buffer.
240                 // The remaining bytes are the IV copy those into a buffer as well.
241                 //
242                 Array.Copy(decryptedKeyAndIV, decryptionKey, decryptionKey.Length);
243                 Array.Copy(decryptedKeyAndIV, decryptionKey.Length, decryptionIV, 0, decryptionIV.Length);
244
245                 using (ICryptoTransform decryptor = symmetricAlgorithm.CreateDecryptor(decryptionKey, decryptionIV))
246                 {
247                     return decryptor.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
248                 }
249             }
250         }
251
252         /// <summary>
253         /// Encode the data.  The data is encrypted using the default encryption algorithm (AES-256), 
254         /// then the AES key is encrypted using RSA and the RSA public key is appended.
255         /// </summary>
256         /// <param name="value">The data to encode</param>
257         /// <exception cref="ArgumentNullException">The argument 'value' is null.</exception>
258         /// <exception cref="ArgumentException">The argument 'value' contains zero bytes.</exception>
259         /// <exception cref="InvalidOperationException">The EncryptionKey is null.</exception>
260         /// <returns>Encoded data</returns>
261         public override byte[] Encode(byte[] value)
262         {
263             if (null == value)
264             {
265                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
266             }
267
268             if (0 == value.Length)
269             {
270                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.ID6044));
271             }
272
273             RSA encryptionKey = EncryptionKey;
274
275             if (null == encryptionKey)
276             {
277                 throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6043));
278             }
279
280             byte[] rsaHash;
281             byte[] encryptedKeyAndIV;
282             byte[] encryptedData;
283
284             using (HashAlgorithm hash = CryptoHelper.CreateHashAlgorithm(_hashName))
285             {
286                 rsaHash = hash.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey.ToXmlString(false)));
287             }
288
289             using (SymmetricAlgorithm encryptionAlgorithm = CryptoHelper.NewDefaultEncryption())
290             {
291                 encryptionAlgorithm.GenerateIV();
292                 encryptionAlgorithm.GenerateKey();
293
294                 using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor())
295                 {
296                     encryptedData = encryptor.TransformFinalBlock(value, 0, value.Length);
297                 }
298
299                 RSACryptoServiceProvider provider = encryptionKey as RSACryptoServiceProvider;
300
301                 if ( provider == null )
302                 {
303                     throw DiagnosticUtility.ThrowHelperInvalidOperation( SR.GetString( SR.ID6041 ) );
304                 }
305
306                 //
307                 // Concatenate the Key and IV in an attempt to avoid two minimum block lengths in the cookie
308                 //
309                 byte[] keyAndIV = new byte[encryptionAlgorithm.Key.Length + encryptionAlgorithm.IV.Length];
310                 Array.Copy(encryptionAlgorithm.Key, keyAndIV, encryptionAlgorithm.Key.Length);
311                 Array.Copy(encryptionAlgorithm.IV, 0, keyAndIV, encryptionAlgorithm.Key.Length, encryptionAlgorithm.IV.Length);
312
313                 encryptedKeyAndIV = CngLightup.OaepSha1Encrypt(encryptionKey, keyAndIV);
314             }
315
316             using (MemoryStream ms = new MemoryStream())
317             {
318                 using (BinaryWriter bw = new BinaryWriter(ms))
319                 {
320                     bw.Write(rsaHash);
321                     bw.Write(encryptedKeyAndIV.Length);
322                     bw.Write(encryptedKeyAndIV);
323                     bw.Write(encryptedData.Length);
324                     bw.Write(encryptedData);
325                     bw.Flush();
326                 }
327
328                 return ms.ToArray();
329             }
330         }
331     }
332 }