1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
4 using System.Collections.Generic;
5 using System.Collections.ObjectModel;
7 using System.Security.Cryptography;
8 using System.Security.Cryptography.X509Certificates;
11 namespace System.IdentityModel
14 /// Encrypts a cookie using <see cref="RSA"/>.
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).
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.
28 public class RsaEncryptionCookieTransform : CookieTransform
31 // Produces an encrypted stream as follows:
33 // Hashsha?( RSA.ToString( false ) ) +
34 // Length( EncryptRSA( Key + IV ) +
35 // EncryptRSA( Key + IV ) +
36 // Length( EncryptAES( Data ) +
41 List<RSA> _decryptionKeys = new List<RSA>();
42 string _hashName = "SHA256";
45 /// Creates a new instance of <see cref="RsaEncryptionCookieTransform"/>.
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)
53 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("key");
56 _decryptionKeys.Add(_encryptionKey);
60 /// Creates a new instance of <see cref="RsaEncryptionCookieTransform"/>
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)
68 if (null == certificate)
70 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
72 _encryptionKey = X509Util.EnsureAndGetPrivateRSAKey(certificate);
73 _decryptionKeys.Add(_encryptionKey);
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.
80 internal RsaEncryptionCookieTransform()
85 /// Gets or sets the RSA key used for encryption
87 public virtual RSA EncryptionKey
89 get { return _encryptionKey; }
92 _encryptionKey = value;
93 _decryptionKeys = new List<RSA>(new RSA[] { _encryptionKey });
98 /// Gets the keys used for decryption
99 /// By default, this property returns a list containing only the encryption key.
101 protected virtual ReadOnlyCollection<RSA> DecryptionKeys
105 return _decryptionKeys.AsReadOnly();
110 /// Gets or sets the name of the hash algorithm to use.
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".
116 public string HashName
118 get { return _hashName; }
121 using (HashAlgorithm algorithm = CryptoHelper.CreateHashAlgorithm(value))
123 if (algorithm == null)
125 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.ID6034, value));
133 /// Decrypts data using the provided RSA key(s) to decrypt an AES key, which decrypts the cookie.
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)
145 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("encoded");
148 if (0 == encoded.Length)
150 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("encoded", SR.GetString(SR.ID6045));
153 ReadOnlyCollection<RSA> decryptionKeys = DecryptionKeys;
155 if (0 == decryptionKeys.Count)
157 throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6039));
160 byte[] encryptedKeyAndIV;
161 byte[] encryptedData;
163 RSA rsaDecryptionKey = null;
165 using (HashAlgorithm hash = CryptoHelper.CreateHashAlgorithm(_hashName))
167 int hashSizeInBytes = hash.HashSize / 8;
168 using (BinaryReader br = new BinaryReader(new MemoryStream(encoded)))
170 rsaHash = br.ReadBytes(hashSizeInBytes);
171 int encryptedKeyAndIVSize = br.ReadInt32();
172 if (encryptedKeyAndIVSize < 0)
174 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1006, encryptedKeyAndIVSize)));
177 // Enforce upper limit on key size to prevent large buffer allocation in br.ReadBytes()
180 if (encryptedKeyAndIVSize > encoded.Length)
182 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1007)));
184 encryptedKeyAndIV = br.ReadBytes(encryptedKeyAndIVSize);
186 int encryptedDataSize = br.ReadInt32();
187 if (encryptedDataSize < 0)
189 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1008, encryptedDataSize)));
192 // Enforce upper limit on data size to prevent large buffer allocation in br.ReadBytes()
194 if (encryptedDataSize > encoded.Length)
196 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1009)));
199 encryptedData = br.ReadBytes(encryptedDataSize);
203 // Find the decryption key matching the one in XML
205 foreach (RSA key in decryptionKeys)
207 byte[] hashedKey = hash.ComputeHash(Encoding.UTF8.GetBytes(key.ToXmlString(false)));
208 if (CryptoHelper.IsEqual(hashedKey, rsaHash))
210 rsaDecryptionKey = key;
216 if (rsaDecryptionKey == null)
218 throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6040));
221 byte[] decryptedKeyAndIV = CngLightup.OaepSha1Decrypt(rsaDecryptionKey, encryptedKeyAndIV);
223 using (SymmetricAlgorithm symmetricAlgorithm = CryptoHelper.NewDefaultEncryption())
226 byte[] decryptionKey = new byte[symmetricAlgorithm.KeySize / 8];
229 // Ensure there is sufficient length in the descrypted key and IV buffer for an IV.
231 if (decryptedKeyAndIV.Length < decryptionKey.Length)
233 throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6047, decryptedKeyAndIV.Length, decryptionKey.Length));
236 byte[] decryptionIV = new byte[decryptedKeyAndIV.Length - decryptionKey.Length];
239 // Copy key into its own buffer.
240 // The remaining bytes are the IV copy those into a buffer as well.
242 Array.Copy(decryptedKeyAndIV, decryptionKey, decryptionKey.Length);
243 Array.Copy(decryptedKeyAndIV, decryptionKey.Length, decryptionIV, 0, decryptionIV.Length);
245 using (ICryptoTransform decryptor = symmetricAlgorithm.CreateDecryptor(decryptionKey, decryptionIV))
247 return decryptor.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
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.
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)
265 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
268 if (0 == value.Length)
270 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.ID6044));
273 RSA encryptionKey = EncryptionKey;
275 if (null == encryptionKey)
277 throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6043));
281 byte[] encryptedKeyAndIV;
282 byte[] encryptedData;
284 using (HashAlgorithm hash = CryptoHelper.CreateHashAlgorithm(_hashName))
286 rsaHash = hash.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey.ToXmlString(false)));
289 using (SymmetricAlgorithm encryptionAlgorithm = CryptoHelper.NewDefaultEncryption())
291 encryptionAlgorithm.GenerateIV();
292 encryptionAlgorithm.GenerateKey();
294 using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor())
296 encryptedData = encryptor.TransformFinalBlock(value, 0, value.Length);
299 RSACryptoServiceProvider provider = encryptionKey as RSACryptoServiceProvider;
301 if ( provider == null )
303 throw DiagnosticUtility.ThrowHelperInvalidOperation( SR.GetString( SR.ID6041 ) );
307 // Concatenate the Key and IV in an attempt to avoid two minimum block lengths in the cookie
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);
313 encryptedKeyAndIV = CngLightup.OaepSha1Encrypt(encryptionKey, keyAndIV);
316 using (MemoryStream ms = new MemoryStream())
318 using (BinaryWriter bw = new BinaryWriter(ms))
321 bw.Write(encryptedKeyAndIV.Length);
322 bw.Write(encryptedKeyAndIV);
323 bw.Write(encryptedData.Length);
324 bw.Write(encryptedData);