//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; namespace System.IdentityModel { /// /// Encrypts a cookie using . /// /// /// /// Cookies encrypted with this transform may be decrypted /// by any machine that shares the same RSA private key (generally /// associated with an X509 certificate). /// /// /// The given data is encrypted using a random AES256 key. This key is /// then encrypted using RSA, and the RSA public key is sent in plain text /// so that when decoding the class knows which RSA key to use. /// /// public class RsaEncryptionCookieTransform : CookieTransform { // // Produces an encrypted stream as follows: // // Hashsha?( RSA.ToString( false ) ) + // Length( EncryptRSA( Key + IV ) + // EncryptRSA( Key + IV ) + // Length( EncryptAES( Data ) + // EncryptAES( Data ) // RSA _encryptionKey; List _decryptionKeys = new List(); string _hashName = "SHA256"; /// /// Creates a new instance of . /// /// The provided key will be used as the encryption and decryption key by default. /// When the key is null. public RsaEncryptionCookieTransform( RSA key ) { if ( null == key ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull( "key" ); } _encryptionKey = key; _decryptionKeys.Add( _encryptionKey ); } /// /// Creates a new instance of /// /// Certificate whose private key is used to encrypt and decrypt. /// When certificate is null. /// When the certificate has no private key. /// When the certificate's key is not RSA. public RsaEncryptionCookieTransform( X509Certificate2 certificate ) { if ( null == certificate ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull( "certificate" ); } _encryptionKey = X509Util.EnsureAndGetPrivateRSAKey( certificate ); _decryptionKeys.Add( _encryptionKey ); } /// /// Creates a new instance of . /// The instance created by this constructor is not usable until the signing and verification keys are set. /// internal RsaEncryptionCookieTransform() { } /// /// Gets or sets the RSA key used for encryption /// public virtual RSA EncryptionKey { get { return _encryptionKey; } set { _encryptionKey = value; _decryptionKeys = new List( new RSA[] { _encryptionKey }); } } /// /// Gets the keys used for decryption /// By default, this property returns a list containing only the encryption key. /// protected virtual ReadOnlyCollection DecryptionKeys { get { return _decryptionKeys.AsReadOnly(); } } /// /// Gets or sets the name of the hash algorithm to use. /// /// /// SHA256 is the default algorithm. This may require a minimum platform of Windows Server 2003 and .NET 3.5 SP1. /// If SHA256 is not supported, set HashName to "SHA1". /// public string HashName { get { return _hashName; } set { using ( HashAlgorithm algorithm = CryptoHelper.CreateHashAlgorithm( value ) ) { if ( algorithm == null ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument( "value", SR.GetString( SR.ID6034, value ) ); } _hashName = value; } } } /// /// Decrypts data using the provided RSA key(s) to decrypt an AES key, which decrypts the cookie. /// /// The encoded data /// The decoded data /// The argument 'encoded' is null. /// The argument 'encoded' contains zero bytes. /// The platform does not support the requested algorithm. /// There are no decryption keys or none of the keys match. public override byte[] Decode( byte[] encoded ) { if ( null == encoded ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull( "encoded" ); } if ( 0 == encoded.Length ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument( "encoded", SR.GetString( SR.ID6045 ) ); } ReadOnlyCollection decryptionKeys = DecryptionKeys; if ( 0 == decryptionKeys.Count ) { throw DiagnosticUtility.ThrowHelperInvalidOperation( SR.GetString( SR.ID6039 ) ); } byte[] encryptedKeyAndIV; byte[] encryptedData; byte[] rsaHash; RSA rsaDecryptionKey = null; using ( HashAlgorithm hash = CryptoHelper.CreateHashAlgorithm( _hashName ) ) { int hashSizeInBytes = hash.HashSize / 8; using ( BinaryReader br = new BinaryReader( new MemoryStream( encoded ) ) ) { rsaHash = br.ReadBytes( hashSizeInBytes ); int encryptedKeyAndIVSize = br.ReadInt32(); if ( encryptedKeyAndIVSize < 0 ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new FormatException( SR.GetString( SR.ID1006, encryptedKeyAndIVSize ) ) ); } // // Enforce upper limit on key size to prevent large buffer allocation in br.ReadBytes() // if ( encryptedKeyAndIVSize > encoded.Length ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new FormatException( SR.GetString( SR.ID1007 ) ) ); } encryptedKeyAndIV = br.ReadBytes( encryptedKeyAndIVSize ); int encryptedDataSize = br.ReadInt32(); if ( encryptedDataSize < 0 ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new FormatException( SR.GetString( SR.ID1008, encryptedDataSize ) ) ); } // // Enforce upper limit on data size to prevent large buffer allocation in br.ReadBytes() // if ( encryptedDataSize > encoded.Length ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new FormatException( SR.GetString( SR.ID1009 ) ) ); } encryptedData = br.ReadBytes( encryptedDataSize ); } // // Find the decryption key matching the one in XML // foreach ( RSA key in decryptionKeys ) { byte[] hashedKey = hash.ComputeHash( Encoding.UTF8.GetBytes( key.ToXmlString( false ) ) ); if ( CryptoHelper.IsEqual( hashedKey, rsaHash ) ) { rsaDecryptionKey = key; break; } } } if ( rsaDecryptionKey == null ) { throw DiagnosticUtility.ThrowHelperInvalidOperation( SR.GetString( SR.ID6040 ) ); } RSACryptoServiceProvider rsaProvider = rsaDecryptionKey as RSACryptoServiceProvider; if ( rsaProvider == null ) { throw DiagnosticUtility.ThrowHelperInvalidOperation( SR.GetString( SR.ID6041 ) ); } byte[] decryptedKeyAndIV = rsaProvider.Decrypt( encryptedKeyAndIV, true ); using (SymmetricAlgorithm symmetricAlgorithm = CryptoHelper.NewDefaultEncryption()) { byte[] decryptionKey = new byte[symmetricAlgorithm.KeySize / 8]; // // Ensure there is sufficient length in the descrypted key and IV buffer for an IV. // if (decryptedKeyAndIV.Length < decryptionKey.Length) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6047, decryptedKeyAndIV.Length, decryptionKey.Length)); } byte[] decryptionIV = new byte[decryptedKeyAndIV.Length - decryptionKey.Length]; // // Copy key into its own buffer. // The remaining bytes are the IV copy those into a buffer as well. // Array.Copy(decryptedKeyAndIV, decryptionKey, decryptionKey.Length); Array.Copy(decryptedKeyAndIV, decryptionKey.Length, decryptionIV, 0, decryptionIV.Length); using (ICryptoTransform decryptor = symmetricAlgorithm.CreateDecryptor(decryptionKey, decryptionIV)) { return decryptor.TransformFinalBlock(encryptedData, 0, encryptedData.Length); } } } /// /// Encode the data. The data is encrypted using the default encryption algorithm (AES-256), /// then the AES key is encrypted using RSA and the RSA public key is appended. /// /// The data to encode /// The argument 'value' is null. /// The argument 'value' contains zero bytes. /// The EncryptionKey is null. /// Encoded data public override byte[] Encode( byte[] value ) { if ( null == value ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull( "value" ); } if ( 0 == value.Length ) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument( "value", SR.GetString( SR.ID6044 ) ); } RSA encryptionKey = EncryptionKey; if ( null == encryptionKey ) { throw DiagnosticUtility.ThrowHelperInvalidOperation( SR.GetString( SR.ID6043 ) ); } byte[] rsaHash; byte[] encryptedKeyAndIV; byte[] encryptedData; using ( HashAlgorithm hash = CryptoHelper.CreateHashAlgorithm( _hashName ) ) { rsaHash = hash.ComputeHash( Encoding.UTF8.GetBytes( encryptionKey.ToXmlString( false ) ) ); } using ( SymmetricAlgorithm encryptionAlgorithm = CryptoHelper.NewDefaultEncryption() ) { encryptionAlgorithm.GenerateIV(); encryptionAlgorithm.GenerateKey(); using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor()) { encryptedData = encryptor.TransformFinalBlock(value, 0, value.Length); } RSACryptoServiceProvider provider = encryptionKey as RSACryptoServiceProvider; if ( provider == null ) { throw DiagnosticUtility.ThrowHelperInvalidOperation( SR.GetString( SR.ID6041 ) ); } // // Concatenate the Key and IV in an attempt to avoid two minimum block lengths in the cookie // byte[] keyAndIV = new byte[encryptionAlgorithm.Key.Length + encryptionAlgorithm.IV.Length]; Array.Copy( encryptionAlgorithm.Key, keyAndIV, encryptionAlgorithm.Key.Length ); Array.Copy( encryptionAlgorithm.IV, 0, keyAndIV, encryptionAlgorithm.Key.Length, encryptionAlgorithm.IV.Length ); encryptedKeyAndIV = provider.Encrypt( keyAndIV, true ); } using ( MemoryStream ms = new MemoryStream() ) { using ( BinaryWriter bw = new BinaryWriter( ms ) ) { bw.Write( rsaHash ); bw.Write( encryptedKeyAndIV.Length ); bw.Write( encryptedKeyAndIV ); bw.Write( encryptedData.Length ); bw.Write( encryptedData ); bw.Flush(); } return ms.ToArray(); } } } }