/** * TLSSecurityParameters * * This class encapsulates all the security parameters that get negotiated * during the TLS handshake. It also holds all the key derivation methods. * Copyright (c) 2007 Henri Torgemane * * See LICENSE.txt for full license information. */ package com.hurlant.crypto.tls { import com.hurlant.crypto.hash.MD5; import com.hurlant.crypto.hash.SHA1; import com.hurlant.util.Hex; import flash.utils.ByteArray; public class SSLSecurityParameters implements ISecurityParameters { // COMPRESSION public static const COMPRESSION_NULL:uint = 0; private var entity:uint; // SERVER | CLIENT private var bulkCipher:uint; // BULK_CIPHER_* private var cipherType:uint; // STREAM_CIPHER | BLOCK_CIPHER private var keySize:uint; private var keyMaterialLength:uint; private var keyBlock:ByteArray; private var IVSize:uint; private var MAC_length:uint; private var macAlgorithm:uint; // MAC_* private var hashSize:uint; private var compression:uint; // COMPRESSION_NULL private var masterSecret:ByteArray; // 48 bytes private var clientRandom:ByteArray; // 32 bytes private var serverRandom:ByteArray; // 32 bytes private var pad_1:ByteArray; // varies private var pad_2:ByteArray; // varies private var ignoreCNMismatch:Boolean = true; private var trustAllCerts:Boolean = false; private var trustSelfSigned:Boolean = false; public static const PROTOCOL_VERSION:uint = 0x0300; // not strictly speaking part of this, but yeah. public var keyExchange:uint; public function get version() : uint { return PROTOCOL_VERSION; } public function SSLSecurityParameters(entity:uint, localCert:ByteArray = null, localKey:ByteArray = null) { this.entity = entity; reset(); } public function reset():void { bulkCipher = BulkCiphers.NULL; cipherType = BulkCiphers.BLOCK_CIPHER; macAlgorithm = MACs.NULL; compression = COMPRESSION_NULL; masterSecret = null; } public function getBulkCipher():uint { return bulkCipher; } public function getCipherType():uint { return cipherType; } public function getMacAlgorithm():uint { return macAlgorithm; } public function setCipher(cipher:uint):void { bulkCipher = CipherSuites.getBulkCipher(cipher); cipherType = BulkCiphers.getType(bulkCipher); keySize = BulkCiphers.getExpandedKeyBytes(bulkCipher); // 8 keyMaterialLength = BulkCiphers.getKeyBytes(bulkCipher); // 5 IVSize = BulkCiphers.getIVSize(bulkCipher); keyExchange = CipherSuites.getKeyExchange(cipher); macAlgorithm = CipherSuites.getMac(cipher); hashSize = MACs.getHashSize(macAlgorithm); pad_1 = new ByteArray(); pad_2 = new ByteArray(); for (var x:int = 0; x < 48; x++) { pad_1.writeByte(0x36); pad_2.writeByte(0x5c); } } public function setCompression(algo:uint):void { compression = algo; } public function setPreMasterSecret(secret:ByteArray):void { /* Warning! Following code may cause madness Tread not here, unless ye be men of valor. ***** Official Prophylactic Comment ****** (to protect the unwary...this code actually works, that's all you need to know) This does two things, computes the master secret, and generates the keyBlock To compute the master_secret, the following algorithm is used. for SSL 3, this means master = MD5( premaster + SHA1('A' + premaster + client_random + server_random ) ) + MD5( premaster + SHA1('BB' + premaster + client_random + server_random ) ) + MD5( premaster + SHA1('CCC' + premaster + client_random + server_random ) ) */ var tempHashA:ByteArray = new ByteArray(); // temporary hash, gets reused a lot var tempHashB:ByteArray = new ByteArray(); // temporary hash, gets reused a lot var shaHash:ByteArray; var mdHash:ByteArray; var i:int; var j:int; var sha:SHA1 = new SHA1(); var md:MD5 = new MD5(); var k:ByteArray = new ByteArray(); k.writeBytes(secret); k.writeBytes(clientRandom); k.writeBytes(serverRandom); masterSecret = new ByteArray(); var pad_char:uint = 0x41; for ( i = 0; i < 3; i++) { // SHA portion tempHashA.position = 0; for ( j = 0; j < i + 1; j++) { tempHashA.writeByte(pad_char); } pad_char++; tempHashA.writeBytes(k); shaHash = sha.hash(tempHashA); // MD5 portion tempHashB.position = 0; tempHashB.writeBytes(secret); tempHashB.writeBytes(shaHash); mdHash = md.hash(tempHashB); // copy into my key masterSecret.writeBytes(mdHash); } // *************** END MASTER SECRET ************** // More prophylactic comments // *************** START KEY BLOCK **************** // So here, I'm setting up the keyBlock array that I will derive MACs, keys, and IVs from. // Rebuild k (hash seed) k.position = 0; k.writeBytes(masterSecret); k.writeBytes(serverRandom); k.writeBytes(clientRandom); keyBlock = new ByteArray(); tempHashA = new ByteArray(); tempHashB = new ByteArray(); // now for 16 iterations to get 256 bytes (16 * 16), better to have more than not enough pad_char = 0x41; for ( i = 0; i < 16; i++) { tempHashA.position = 0; for ( j = 0; j < i + 1; j++) { tempHashA.writeByte(pad_char); } pad_char++; tempHashA.writeBytes(k); shaHash = sha.hash(tempHashA); tempHashB.position = 0; tempHashB.writeBytes(masterSecret); tempHashB.writeBytes(shaHash, 0); mdHash = md.hash(tempHashB); keyBlock.writeBytes(mdHash); } } public function setClientRandom(secret:ByteArray):void { clientRandom = secret; } public function setServerRandom(secret:ByteArray):void { serverRandom = secret; } public function get useRSA():Boolean { return KeyExchanges.useRSA(keyExchange); } // This is the Finished message // if you value your sanity, stay away...far away public function computeVerifyData(side:uint, handshakeMessages:ByteArray):ByteArray { // for SSL 3.0, this consists of // finished = md5( masterSecret + pad2 + md5( handshake + sender + masterSecret + pad1 ) ) + // sha1( masterSecret + pad2 + sha1( handshake + sender + masterSecret + pad1 ) ) // trace("Handshake messages: " + Hex.fromArray(handshakeMessages)); var sha:SHA1 = new SHA1(); var md:MD5 = new MD5(); var k:ByteArray = new ByteArray(); // handshake + sender + masterSecret + pad1 var j:ByteArray = new ByteArray(); // masterSecret + pad2 + k var innerKey:ByteArray; var outerKey:ByteArray = new ByteArray(); var hashSha:ByteArray; var hashMD:ByteArray; var sideBytes:ByteArray = new ByteArray(); if (side == TLSEngine.CLIENT) { sideBytes.writeUnsignedInt(0x434C4E54); } else { sideBytes.writeUnsignedInt(0x53525652); } // Do the SHA1 part of the routine first masterSecret.position = 0; k.writeBytes(handshakeMessages); k.writeBytes(sideBytes); k.writeBytes(masterSecret); k.writeBytes(pad_1, 0, 40); // limited to 40 chars for SHA1 innerKey = sha.hash(k); // trace("Inner SHA Key: " + Hex.fromArray(innerKey)); j.writeBytes(masterSecret); j.writeBytes(pad_2, 0, 40); // limited to 40 chars for SHA1 j.writeBytes(innerKey); hashSha = sha.hash(j); // trace("Outer SHA Key: " + Hex.fromArray(hashSha)); // Rebuild k for MD5 k = new ByteArray(); k.writeBytes(handshakeMessages); k.writeBytes(sideBytes); k.writeBytes(masterSecret); k.writeBytes(pad_1); // Take the whole length of pad_1 & pad_2 for MD5 innerKey = md.hash(k); // trace("Inner MD5 Key: " + Hex.fromArray(innerKey)); j = new ByteArray(); j.writeBytes(masterSecret); j.writeBytes(pad_2); // see above re: 48 byte pad j.writeBytes(innerKey); hashMD = md.hash(j); // trace("Outer MD5 Key: " + Hex.fromArray(hashMD)); outerKey.writeBytes(hashMD, 0, hashMD.length); outerKey.writeBytes(hashSha, 0, hashSha.length); var out:String = Hex.fromArray(outerKey); // trace("Finished Message: " + out); outerKey.position = 0; return outerKey; } public function computeCertificateVerify( side:uint, handshakeMessages:ByteArray ):ByteArray { // TODO: Implement this, but I don't forsee it being necessary at this point in time, since for purposes // of the override, I'm only going to use TLS return null; } public function getConnectionStates():Object { if (masterSecret != null) { // so now, I have to derive the actual keys from the keyblock that I generated in setPremasterSecret. // for MY purposes, I need RSA-AES 128/256 + SHA // so I'm gonna have keylen = 32, minlen = 32, mac_length = 20, iv_length = 16 // but...I can get this data from the settings returned in the constructor when this object is // It strikes me that TLS does this more elegantly... var mac_length:int = hashSize as Number; var key_length:int = keySize as Number; var iv_length:int = IVSize as Number; var client_write_MAC:ByteArray = new ByteArray(); var server_write_MAC:ByteArray = new ByteArray(); var client_write_key:ByteArray = new ByteArray(); var server_write_key:ByteArray = new ByteArray(); var client_write_IV:ByteArray = new ByteArray(); var server_write_IV:ByteArray = new ByteArray(); // Derive the keys from the keyblock // Get the MACs first keyBlock.position = 0; keyBlock.readBytes(client_write_MAC, 0, mac_length); keyBlock.readBytes(server_write_MAC, 0, mac_length); // keyBlock.position is now at MAC_length * 2 // then get the keys keyBlock.readBytes(client_write_key, 0, key_length); keyBlock.readBytes(server_write_key, 0, key_length); // keyBlock.position is now at (MAC_length * 2) + (keySize * 2) // and then the IVs keyBlock.readBytes(client_write_IV, 0, iv_length); keyBlock.readBytes(server_write_IV, 0, iv_length); // reset this in case it's needed, for some reason or another, but I doubt it keyBlock.position = 0; var client_write:SSLConnectionState = new SSLConnectionState( bulkCipher, cipherType, macAlgorithm, client_write_MAC, client_write_key, client_write_IV); var server_write:SSLConnectionState = new SSLConnectionState( bulkCipher, cipherType, macAlgorithm, server_write_MAC, server_write_key, server_write_IV); if (entity == TLSEngine.CLIENT) { return {read:server_write, write:client_write}; } else { return {read:client_write, write:server_write}; } } else { return {read:new SSLConnectionState, write:new SSLConnectionState}; } } } }