/* Transport Security Layer (TLS) * Copyright (c) 2003 Carlos Guzmán Álvarez * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ using System; using System.IO; using System.Text; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Mono.Security; using Mono.Security.Cryptography; namespace Mono.Security.Protocol.Tls { internal abstract class TlsAbstractCipherSuite { #region FIELDS protected short code; protected string name; protected string algName; protected string hashName; protected bool isExportable; protected CipherMode cipherMode; protected byte keyMaterialSize; protected byte expandedKeyMaterialSize; protected short effectiveKeyBits; protected byte ivSize; protected byte blockSize; protected TlsSessionContext context; protected SymmetricAlgorithm encryptionAlgorithm; protected ICryptoTransform encryptionCipher; protected SymmetricAlgorithm decryptionAlgorithm; protected ICryptoTransform decryptionCipher; protected KeyedHashAlgorithm clientHMAC; protected KeyedHashAlgorithm serverHMAC; #endregion #region PROPERTIES public short Code { get { return code; } } public string Name { get { return name; } } public bool IsExportable { get { return isExportable; } } public CipherMode CipherMode { get { return cipherMode; } } public int HashSize { get { return (int)(hashName == "MD5" ? 16 : 20); } } public byte KeyMaterialSize { get { return keyMaterialSize; } } public int KeyBlockSize { get { return keyMaterialSize*2 + HashSize*2 + ivSize*2; } } public byte ExpandedKeyMaterialSize { get { return expandedKeyMaterialSize; } } public byte EffectiveKeyBits { get { return EffectiveKeyBits; } } public byte IvSize { get { return ivSize; } } public byte BlockSize { get { return blockSize; } } public string HashName { get { return hashName; } } public TlsSessionContext Context { get { return context; } set { context = value; } } public KeyedHashAlgorithm ClientHMAC { get { return clientHMAC; } } public KeyedHashAlgorithm ServerHMAC { get { return serverHMAC; } } #endregion #region CONSTRUCTORS public TlsAbstractCipherSuite(short code, string name, string algName, string hashName, bool exportable, bool blockMode, byte keyMaterialSize, byte expandedKeyMaterialSize, short effectiveKeyBytes, byte ivSize, byte blockSize) { this.code = code; this.name = name; this.algName = algName; this.hashName = hashName; this.isExportable = exportable; if (blockMode) { this.cipherMode = CipherMode.CBC; } this.keyMaterialSize = keyMaterialSize; this.expandedKeyMaterialSize = expandedKeyMaterialSize; this.effectiveKeyBits = effectiveKeyBits; this.ivSize = ivSize; this.blockSize = blockSize; } #endregion #region METHODS public RSACryptoServiceProvider CreateRSA(X509Certificate certificate) { RSAParameters rsaParams = new RSAParameters(); // for RSA m_publickey contains 2 ASN.1 integers // the modulus and the public exponent ASN1 pubkey = new ASN1(certificate.GetPublicKey()); ASN1 modulus = pubkey [0]; if ((modulus == null) || (modulus.Tag != 0x02)) { return null; } ASN1 exponent = pubkey [1]; if (exponent.Tag != 0x02) { return null; } rsaParams.Modulus = getUnsignedBigInteger(modulus.Value); rsaParams.Exponent = exponent.Value; return CreateRSA(rsaParams); } public RSACryptoServiceProvider CreateRSA(RSAParameters rsaParams) { // BUG: MS BCL 1.0 can't import a key which // isn't the same size as the one present in // the container. int keySize = (rsaParams.Modulus.Length << 3); RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(keySize); rsa.ImportParameters(rsaParams); return rsa; } public void InitializeCipher() { createEncryptionCipher(); createDecryptionCipher(); } public void UpdateClientCipherIV(byte[] iv) { if (cipherMode == CipherMode.CBC) { // Set the new IV encryptionAlgorithm.IV = iv; // Create encryption cipher with the new IV encryptionCipher = encryptionAlgorithm.CreateEncryptor(); } } public void UpdateServerCipherIV(byte[] iv) { if (cipherMode == CipherMode.CBC) { // Set the new IV decryptionAlgorithm.IV = iv; // Create encryption cipher with the new IV decryptionCipher = decryptionAlgorithm.CreateDecryptor(); } } #endregion #region ABSTRACT_METHODS public abstract byte[] EncryptRecord(byte[] fragment, byte[] mac); public abstract void DecryptRecord(byte[] fragment, ref byte[] dcrFragment, ref byte[] dcrMAC); public abstract void CreateMasterSecret(byte[] preMasterSecret); public abstract void CreateKeys(); #endregion #region KEY_GENERATION_METODS public byte[] CreatePremasterSecret() { TlsStream stream = new TlsStream(); // Write protocol version stream.Write((short)this.context.Protocol); // Generate random bytes stream.Write(this.context.GetSecureRandomBytes(46)); byte[] preMasterSecret = stream.ToArray(); stream.Reset(); return preMasterSecret; } public byte[] PRF(byte[] secret, string label, byte[] data, int length) { MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); int secretLen = secret.Length / 2; // Seed TlsStream seedStream = new TlsStream(); seedStream.Write(Encoding.ASCII.GetBytes(label)); seedStream.Write(data); byte[] seed = seedStream.ToArray(); seedStream.Reset(); // Secret 1 byte[] secret1 = new byte[secretLen]; System.Array.Copy(secret, 0, secret1, 0, secretLen); // Secret2 byte[] secret2 = new byte[secretLen]; System.Array.Copy(secret, secretLen, secret2, 0, secretLen); // Secret 1 processing byte[] p_md5 = Expand("MD5", secret1, seed, length); // Secret 2 processing byte[] p_sha = Expand("SHA1", secret2, seed, length); // Perfor XOR of both results byte[] masterSecret = new byte[length]; for (int i = 0; i < masterSecret.Length; i++) { masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]); } return masterSecret; } public byte[] Expand(string hashName, byte[] secret, byte[] seed, int length) { int hashLength = hashName == "MD5" ? 16 : 20; int iterations = (int)(length / hashLength); if ((length % hashLength) > 0) { iterations++; } HMAC hmac = new HMAC(hashName, secret); TlsStream resMacs = new TlsStream(); byte[][] hmacs = new byte[iterations + 1][]; hmacs[0] = seed; for (int i = 1; i <= iterations; i++) { TlsStream hcseed = new TlsStream(); hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length); hmacs[i] = hmac.Hash; hcseed.Write(hmacs[i]); hcseed.Write(seed); hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length); resMacs.Write(hmac.Hash); hcseed.Reset(); } byte[] res = new byte[length]; System.Array.Copy(resMacs.ToArray(), 0, res, 0, res.Length); resMacs.Reset(); return res; } #endregion #region PRIVATE_METHODS // This code is from Mono.Security.X509Certificate class. private byte[] getUnsignedBigInteger(byte[] integer) { if (integer[0] == 0x00) { // this first byte is added so we're sure it's an unsigned integer // however we can't feed it into RSAParameters or DSAParameters int length = integer.Length - 1; byte[] uinteger = new byte[length]; Array.Copy(integer, 1, uinteger, 0, length); return uinteger; } else { return integer; } } private void createEncryptionCipher() { // Create and configure the symmetric algorithm switch (this.algName) { case "RC4": encryptionAlgorithm = new ARC4Managed(); break; default: encryptionAlgorithm = SymmetricAlgorithm.Create(algName); break; } // If it's a block cipher if (cipherMode == CipherMode.CBC) { // Configure encrypt algorithm encryptionAlgorithm.Mode = this.cipherMode; encryptionAlgorithm.Padding = PaddingMode.PKCS7; encryptionAlgorithm.KeySize = this.keyMaterialSize * 8; encryptionAlgorithm.BlockSize = this.blockSize * 8; } // Set the key and IV for the algorithm encryptionAlgorithm.Key = context.ClientWriteKey; encryptionAlgorithm.IV = context.ClientWriteIV; // Create encryption cipher encryptionCipher = encryptionAlgorithm.CreateEncryptor(); // Create the HMAC algorithm for the client clientHMAC = new HMAC(hashName, context.ClientWriteMAC); } private void createDecryptionCipher() { // Create and configure the symmetric algorithm switch (this.algName) { case "RC4": decryptionAlgorithm = new ARC4Managed(); break; default: decryptionAlgorithm = SymmetricAlgorithm.Create(algName); break; } // If it's a block cipher if (cipherMode == CipherMode.CBC) { // Configure encrypt algorithm decryptionAlgorithm.Mode = this.cipherMode; decryptionAlgorithm.Padding = PaddingMode.None; decryptionAlgorithm.KeySize = this.keyMaterialSize * 8; decryptionAlgorithm.BlockSize = this.blockSize * 8; } // Set the key and IV for the algorithm decryptionAlgorithm.Key = context.ServerWriteKey; decryptionAlgorithm.IV = context.ServerWriteIV; // Create decryption cipher decryptionCipher = decryptionAlgorithm.CreateDecryptor(); // Create the HMAC algorithm for the server serverHMAC = new HMAC(hashName, context.ServerWriteMAC); } #endregion } }