2 * TLSSecurityParameters
\r
4 * This class encapsulates all the security parameters that get negotiated
\r
5 * during the TLS handshake. It also holds all the key derivation methods.
\r
6 * Copyright (c) 2007 Henri Torgemane
\r
8 * See LICENSE.txt for full license information.
\r
10 package com.hurlant.crypto.tls {
\r
11 import com.hurlant.crypto.hash.MD5;
\r
12 import com.hurlant.crypto.hash.SHA1;
\r
13 import com.hurlant.util.Hex;
\r
15 import flash.utils.ByteArray;
\r
17 public class SSLSecurityParameters implements ISecurityParameters {
\r
20 public static const COMPRESSION_NULL:uint = 0;
\r
22 private var entity:uint; // SERVER | CLIENT
\r
23 private var bulkCipher:uint; // BULK_CIPHER_*
\r
24 private var cipherType:uint; // STREAM_CIPHER | BLOCK_CIPHER
\r
25 private var keySize:uint;
\r
26 private var keyMaterialLength:uint;
\r
27 private var keyBlock:ByteArray;
\r
28 private var IVSize:uint;
\r
29 private var MAC_length:uint;
\r
30 private var macAlgorithm:uint; // MAC_*
\r
31 private var hashSize:uint;
\r
32 private var compression:uint; // COMPRESSION_NULL
\r
33 private var masterSecret:ByteArray; // 48 bytes
\r
34 private var clientRandom:ByteArray; // 32 bytes
\r
35 private var serverRandom:ByteArray; // 32 bytes
\r
36 private var pad_1:ByteArray; // varies
\r
37 private var pad_2:ByteArray; // varies
\r
38 private var ignoreCNMismatch:Boolean = true;
\r
39 private var trustAllCerts:Boolean = false;
\r
40 private var trustSelfSigned:Boolean = false;
\r
41 public static const PROTOCOL_VERSION:uint = 0x0300;
\r
43 // not strictly speaking part of this, but yeah.
\r
44 public var keyExchange:uint;
\r
46 public function get version() : uint {
\r
47 return PROTOCOL_VERSION;
\r
49 public function SSLSecurityParameters(entity:uint, localCert:ByteArray = null, localKey:ByteArray = null) {
\r
50 this.entity = entity;
\r
54 public function reset():void {
\r
55 bulkCipher = BulkCiphers.NULL;
\r
56 cipherType = BulkCiphers.BLOCK_CIPHER;
\r
57 macAlgorithm = MACs.NULL;
\r
58 compression = COMPRESSION_NULL;
\r
59 masterSecret = null;
\r
62 public function getBulkCipher():uint {
\r
65 public function getCipherType():uint {
\r
68 public function getMacAlgorithm():uint {
\r
69 return macAlgorithm;
\r
72 public function setCipher(cipher:uint):void {
\r
73 bulkCipher = CipherSuites.getBulkCipher(cipher);
\r
74 cipherType = BulkCiphers.getType(bulkCipher);
\r
75 keySize = BulkCiphers.getExpandedKeyBytes(bulkCipher); // 8
\r
76 keyMaterialLength = BulkCiphers.getKeyBytes(bulkCipher); // 5
\r
77 IVSize = BulkCiphers.getIVSize(bulkCipher);
\r
80 keyExchange = CipherSuites.getKeyExchange(cipher);
\r
82 macAlgorithm = CipherSuites.getMac(cipher);
\r
83 hashSize = MACs.getHashSize(macAlgorithm);
\r
84 pad_1 = new ByteArray();
\r
85 pad_2 = new ByteArray();
\r
86 for (var x:int = 0; x < 48; x++) {
\r
87 pad_1.writeByte(0x36);
\r
88 pad_2.writeByte(0x5c);
\r
91 public function setCompression(algo:uint):void {
\r
95 public function setPreMasterSecret(secret:ByteArray):void {
\r
96 /* Warning! Following code may cause madness
\r
97 Tread not here, unless ye be men of valor.
\r
99 ***** Official Prophylactic Comment ******
\r
100 (to protect the unwary...this code actually works, that's all you need to know)
\r
102 This does two things, computes the master secret, and generates the keyBlock
\r
105 To compute the master_secret, the following algorithm is used.
\r
106 for SSL 3, this means
\r
107 master = MD5( premaster + SHA1('A' + premaster + client_random + server_random ) ) +
\r
108 MD5( premaster + SHA1('BB' + premaster + client_random + server_random ) ) +
\r
109 MD5( premaster + SHA1('CCC' + premaster + client_random + server_random ) )
\r
111 var tempHashA:ByteArray = new ByteArray(); // temporary hash, gets reused a lot
\r
112 var tempHashB:ByteArray = new ByteArray(); // temporary hash, gets reused a lot
\r
114 var shaHash:ByteArray;
\r
115 var mdHash:ByteArray;
\r
120 var sha:SHA1 = new SHA1();
\r
121 var md:MD5 = new MD5();
\r
123 var k:ByteArray = new ByteArray();
\r
125 k.writeBytes(secret);
\r
126 k.writeBytes(clientRandom);
\r
127 k.writeBytes(serverRandom);
\r
129 masterSecret = new ByteArray();
\r
130 var pad_char:uint = 0x41;
\r
132 for ( i = 0; i < 3; i++) {
\r
134 tempHashA.position = 0;
\r
136 for ( j = 0; j < i + 1; j++) {
\r
137 tempHashA.writeByte(pad_char);
\r
141 tempHashA.writeBytes(k);
\r
142 shaHash = sha.hash(tempHashA);
\r
145 tempHashB.position = 0;
\r
146 tempHashB.writeBytes(secret);
\r
147 tempHashB.writeBytes(shaHash);
\r
148 mdHash = md.hash(tempHashB);
\r
150 // copy into my key
\r
151 masterSecret.writeBytes(mdHash);
\r
154 // *************** END MASTER SECRET **************
\r
156 // More prophylactic comments
\r
157 // *************** START KEY BLOCK ****************
\r
159 // So here, I'm setting up the keyBlock array that I will derive MACs, keys, and IVs from.
\r
160 // Rebuild k (hash seed)
\r
163 k.writeBytes(masterSecret);
\r
164 k.writeBytes(serverRandom);
\r
165 k.writeBytes(clientRandom);
\r
167 keyBlock = new ByteArray();
\r
169 tempHashA = new ByteArray();
\r
170 tempHashB = new ByteArray();
\r
171 // now for 16 iterations to get 256 bytes (16 * 16), better to have more than not enough
\r
173 for ( i = 0; i < 16; i++) {
\r
174 tempHashA.position = 0;
\r
176 for ( j = 0; j < i + 1; j++) {
\r
177 tempHashA.writeByte(pad_char);
\r
180 tempHashA.writeBytes(k);
\r
181 shaHash = sha.hash(tempHashA);
\r
183 tempHashB.position = 0;
\r
184 tempHashB.writeBytes(masterSecret);
\r
185 tempHashB.writeBytes(shaHash, 0);
\r
186 mdHash = md.hash(tempHashB);
\r
188 keyBlock.writeBytes(mdHash);
\r
192 public function setClientRandom(secret:ByteArray):void {
\r
193 clientRandom = secret;
\r
195 public function setServerRandom(secret:ByteArray):void {
\r
196 serverRandom = secret;
\r
199 public function get useRSA():Boolean {
\r
200 return KeyExchanges.useRSA(keyExchange);
\r
203 // This is the Finished message
\r
204 // if you value your sanity, stay away...far away
\r
205 public function computeVerifyData(side:uint, handshakeMessages:ByteArray):ByteArray {
\r
206 // for SSL 3.0, this consists of
\r
207 // finished = md5( masterSecret + pad2 + md5( handshake + sender + masterSecret + pad1 ) ) +
\r
208 // sha1( masterSecret + pad2 + sha1( handshake + sender + masterSecret + pad1 ) )
\r
210 // trace("Handshake messages: " + Hex.fromArray(handshakeMessages));
\r
211 var sha:SHA1 = new SHA1();
\r
212 var md:MD5 = new MD5();
\r
213 var k:ByteArray = new ByteArray(); // handshake + sender + masterSecret + pad1
\r
214 var j:ByteArray = new ByteArray(); // masterSecret + pad2 + k
\r
216 var innerKey:ByteArray;
\r
217 var outerKey:ByteArray = new ByteArray();
\r
219 var hashSha:ByteArray;
\r
220 var hashMD:ByteArray;
\r
222 var sideBytes:ByteArray = new ByteArray();
\r
223 if (side == TLSEngine.CLIENT) {
\r
224 sideBytes.writeUnsignedInt(0x434C4E54);
\r
226 sideBytes.writeUnsignedInt(0x53525652);
\r
229 // Do the SHA1 part of the routine first
\r
230 masterSecret.position = 0;
\r
231 k.writeBytes(handshakeMessages);
\r
232 k.writeBytes(sideBytes);
\r
233 k.writeBytes(masterSecret);
\r
234 k.writeBytes(pad_1, 0, 40); // limited to 40 chars for SHA1
\r
236 innerKey = sha.hash(k);
\r
237 // trace("Inner SHA Key: " + Hex.fromArray(innerKey));
\r
239 j.writeBytes(masterSecret);
\r
240 j.writeBytes(pad_2, 0, 40); // limited to 40 chars for SHA1
\r
241 j.writeBytes(innerKey);
\r
243 hashSha = sha.hash(j);
\r
244 // trace("Outer SHA Key: " + Hex.fromArray(hashSha));
\r
246 // Rebuild k for MD5
\r
247 k = new ByteArray();
\r
249 k.writeBytes(handshakeMessages);
\r
250 k.writeBytes(sideBytes);
\r
251 k.writeBytes(masterSecret);
\r
252 k.writeBytes(pad_1); // Take the whole length of pad_1 & pad_2 for MD5
\r
254 innerKey = md.hash(k);
\r
255 // trace("Inner MD5 Key: " + Hex.fromArray(innerKey));
\r
257 j = new ByteArray();
\r
258 j.writeBytes(masterSecret);
\r
259 j.writeBytes(pad_2); // see above re: 48 byte pad
\r
260 j.writeBytes(innerKey);
\r
262 hashMD = md.hash(j);
\r
263 // trace("Outer MD5 Key: " + Hex.fromArray(hashMD));
\r
265 outerKey.writeBytes(hashMD, 0, hashMD.length);
\r
266 outerKey.writeBytes(hashSha, 0, hashSha.length);
\r
267 var out:String = Hex.fromArray(outerKey);
\r
268 // trace("Finished Message: " + out);
\r
269 outerKey.position = 0;
\r
275 public function computeCertificateVerify( side:uint, handshakeMessages:ByteArray ):ByteArray {
\r
276 // TODO: Implement this, but I don't forsee it being necessary at this point in time, since for purposes
\r
277 // of the override, I'm only going to use TLS
\r
281 public function getConnectionStates():Object {
\r
283 if (masterSecret != null) {
\r
284 // so now, I have to derive the actual keys from the keyblock that I generated in setPremasterSecret.
\r
285 // for MY purposes, I need RSA-AES 128/256 + SHA
\r
286 // so I'm gonna have keylen = 32, minlen = 32, mac_length = 20, iv_length = 16
\r
287 // but...I can get this data from the settings returned in the constructor when this object is
\r
288 // It strikes me that TLS does this more elegantly...
\r
290 var mac_length:int = hashSize as Number;
\r
291 var key_length:int = keySize as Number;
\r
292 var iv_length:int = IVSize as Number;
\r
294 var client_write_MAC:ByteArray = new ByteArray();
\r
295 var server_write_MAC:ByteArray = new ByteArray();
\r
296 var client_write_key:ByteArray = new ByteArray();
\r
297 var server_write_key:ByteArray = new ByteArray();
\r
298 var client_write_IV:ByteArray = new ByteArray();
\r
299 var server_write_IV:ByteArray = new ByteArray();
\r
301 // Derive the keys from the keyblock
\r
302 // Get the MACs first
\r
303 keyBlock.position = 0;
\r
304 keyBlock.readBytes(client_write_MAC, 0, mac_length);
\r
305 keyBlock.readBytes(server_write_MAC, 0, mac_length);
\r
307 // keyBlock.position is now at MAC_length * 2
\r
308 // then get the keys
\r
309 keyBlock.readBytes(client_write_key, 0, key_length);
\r
310 keyBlock.readBytes(server_write_key, 0, key_length);
\r
312 // keyBlock.position is now at (MAC_length * 2) + (keySize * 2)
\r
313 // and then the IVs
\r
314 keyBlock.readBytes(client_write_IV, 0, iv_length);
\r
315 keyBlock.readBytes(server_write_IV, 0, iv_length);
\r
317 // reset this in case it's needed, for some reason or another, but I doubt it
\r
318 keyBlock.position = 0;
\r
320 var client_write:SSLConnectionState = new SSLConnectionState(
\r
321 bulkCipher, cipherType, macAlgorithm,
\r
322 client_write_MAC, client_write_key, client_write_IV);
\r
323 var server_write:SSLConnectionState = new SSLConnectionState(
\r
324 bulkCipher, cipherType, macAlgorithm,
\r
325 server_write_MAC, server_write_key, server_write_IV);
\r
327 if (entity == TLSEngine.CLIENT) {
\r
328 return {read:server_write, write:client_write};
\r
330 return {read:client_write, write:server_write};
\r
335 return {read:new SSLConnectionState, write:new SSLConnectionState};
\r