PageRenderTime 212ms CodeModel.GetById 60ms app.highlight 102ms RepoModel.GetById 39ms app.codeStats 1ms

/js/lib/Socket.IO-node/support/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSEngine.as

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
ActionScript | 895 lines | 608 code | 113 blank | 174 comment | 102 complexity | 108e9ed65d14ec468afb038b8a2f1e2d MD5 | raw file
  1/**
  2 * TLSEngine
  3 * 
  4 * A TLS protocol implementation.
  5 * See comment below for some details.
  6 * Copyright (c) 2007 Henri Torgemane
  7 * 
  8 * Patched(heavily) by Bobby Parker (shortwave@gmail.com)
  9 * 
 10 * See LICENSE.txt for full license information.
 11 */
 12package com.hurlant.crypto.tls {
 13	import com.hurlant.crypto.cert.X509Certificate;
 14	import com.hurlant.crypto.cert.X509CertificateCollection;
 15	import com.hurlant.crypto.prng.Random;
 16	import com.hurlant.util.ArrayUtil;
 17	import com.hurlant.util.Hex;
 18	
 19	import flash.events.Event;
 20	import flash.events.EventDispatcher;
 21	import flash.events.ProgressEvent;
 22	import flash.utils.ByteArray;
 23	import flash.utils.IDataInput;
 24	import flash.utils.IDataOutput;
 25	import flash.utils.clearTimeout;
 26	import flash.utils.setTimeout;
 27	import com.hurlant.crypto.prng.ARC4;
 28
 29
 30	[Event(name="close", type="flash.events.Event")]
 31	[Event(name="socketData", type="flash.events.ProgressEvent")]
 32	[Event(name="ready", type="com.hurlant.crypto.tls.TLSEvent")]
 33	[Event(name="data", type="com.hurlant.crypto.tls.TLSEvent")]
 34	
 35	/**
 36	 * The heart of the TLS protocol.
 37	 * This class can work in server or client mode.
 38	 * 
 39	 * This doesn't fully implement the TLS protocol.
 40	 * 
 41	 * Things missing that I'd like to add:
 42	 * - support for client-side certificates
 43	 * - general code clean-up to make sure we don't have gaping securite holes
 44	 * 
 45	 * Things that aren't there that I won't add:
 46	 * - support for "export" cypher suites (deprecated in later TLS versions)
 47	 * - support for "anon" cypher suites (deprecated in later TLS versions)
 48	 * 
 49	 * Things that I'm unsure about adding later:
 50	 * - compression. Compressing encrypted streams is barely worth the CPU cycles.
 51	 * - diffie-hellman based key exchange mechanisms. Nifty, but would we miss it?
 52	 * 
 53	 * @author henri
 54	 * 
 55	 */
 56	public class TLSEngine extends EventDispatcher {
 57		
 58		public static const SERVER:uint = 0;
 59		public static const CLIENT:uint = 1;
 60		public var protocol_version:uint;
 61
 62
 63	
 64		private static const PROTOCOL_HANDSHAKE:uint = 22;
 65		private static const PROTOCOL_ALERT:uint = 21;
 66		private static const PROTOCOL_CHANGE_CIPHER_SPEC:uint = 20;
 67		private static const PROTOCOL_APPLICATION_DATA:uint = 23;
 68
 69
 70		private static const STATE_NEW:uint = 0; // brand new. nothing happened yet
 71		private static const STATE_NEGOTIATING:uint = 1; // we're figuring out what to use
 72		private static const STATE_READY:uint = 2; // we're ready for AppData stuff to go over us.
 73		private static const STATE_CLOSED:uint = 3; // we're done done.
 74		
 75		private var _entity:uint; // SERVER | CLIENT
 76		private var _config:TLSConfig;
 77		
 78		private var _state:uint;
 79		
 80		private var _securityParameters:ISecurityParameters;
 81		
 82		private var _currentReadState:IConnectionState;
 83		private var _currentWriteState:IConnectionState;
 84		private var _pendingReadState:IConnectionState;
 85		private var _pendingWriteState:IConnectionState;
 86		
 87		private var _handshakePayloads:ByteArray;
 88		private var _handshakeRecords:ByteArray; // For client-side certificate verify 
 89		
 90		private var _iStream:IDataInput;
 91		private var _oStream:IDataOutput;
 92		
 93		// temporary store for X509 certs received by this engine.
 94		private var _store:X509CertificateCollection;
 95		// the main certificate received from the other side.
 96		private var _otherCertificate:X509Certificate;
 97		
 98		public function get peerCertificate() : X509Certificate {
 99			return _otherCertificate;
100		}
101		// If this isn't null, we expect this identity to be found in the Cert's Subject CN.
102		private var _otherIdentity:String;
103		
104		// The client-side cert
105		private var _myCertficate:X509Certificate;
106		// My Identity
107		private var _myIdentity:String;
108		
109		/**
110		 * 
111		 * @param config		A TLSConfig instance describing how we're supposed to work
112		 * @param iStream		An input stream to read TLS data from
113		 * @param oStream		An output stream to write TLS data to
114		 * @param otherIdentity	An optional identifier. If set, this will be checked against the Subject CN of the other side's certificate.
115		 * 
116		 */
117		function TLSEngine(config:TLSConfig, iStream:IDataInput, oStream:IDataOutput, otherIdentity:String = null) {
118			_entity = config.entity;
119			_config = config;
120			_iStream = iStream;
121			_oStream = oStream;
122			_otherIdentity = otherIdentity;
123			
124			_state = STATE_NEW;
125			
126			// Pick the right set of callbacks
127			_entityHandshakeHandlers = _entity == CLIENT ? handshakeHandlersClient : handshakeHandlersServer;
128			
129			// setting up new security parameters needs to be controlled by...something.
130			if (_config.version == SSLSecurityParameters.PROTOCOL_VERSION) {
131				_securityParameters = new SSLSecurityParameters(_entity);
132			} else {
133				_securityParameters = new TLSSecurityParameters(_entity, _config.certificate, _config.privateKey);
134			}
135			protocol_version = _config.version;
136			
137			// So this...why is it here, other than to preclude a possible null pointer situation?
138			var states:Object = _securityParameters.getConnectionStates();
139			
140			_currentReadState = states.read;
141			_currentWriteState = states.write;
142			
143			_handshakePayloads = new ByteArray;
144			
145			_store = new X509CertificateCollection;
146		}
147		
148		/**
149		 * This starts the TLS negotiation for a TLS Client.
150		 * 
151		 * This is a no-op for a TLS Server.
152		 * 
153		 */
154		public function start():void {
155			if (_entity == CLIENT) {
156				try {
157					startHandshake();
158				} catch (e:TLSError) {
159					handleTLSError(e);
160				}
161			}
162		}
163		
164		
165		public function dataAvailable(e:* = null):void {
166			if (_state == STATE_CLOSED) return; // ignore
167			try {
168				parseRecord(_iStream);
169			} catch (e:TLSError) {
170				handleTLSError(e);
171			}
172		}
173		
174		public function close(e:TLSError = null):void {
175			if (_state == STATE_CLOSED) return; // ignore
176			// ok. send an Alert to let the peer know
177			var rec:ByteArray = new ByteArray;
178			if (e==null && _state != STATE_READY) {
179				// use canceled while handshaking. be nice about it
180				rec[0] = 1;
181				rec[1] = TLSError.user_canceled;
182				sendRecord(PROTOCOL_ALERT, rec);
183			}
184			rec[0] = 2;
185			if (e == null) {
186				rec[1] = TLSError.close_notify;
187			} else {
188				rec[1] = e.errorID;
189				trace("TLSEngine shutdown triggered by "+e);
190			}
191			sendRecord(PROTOCOL_ALERT, rec);
192			
193			_state = STATE_CLOSED;
194			dispatchEvent(new Event(Event.CLOSE));
195		}
196		
197		private var _packetQueue:Array = [];
198		private function parseRecord(stream:IDataInput):void {
199			var p:ByteArray;
200			while(_state!=STATE_CLOSED && stream.bytesAvailable>4) {
201				
202				if (_packetQueue.length>0) {
203					var packet:Object = _packetQueue.shift();
204					p = packet.data;
205					if (stream.bytesAvailable+p.length>=packet.length) {
206						// we have a whole packet. put together.
207						stream.readBytes(p, p.length, packet.length-p.length);
208						parseOneRecord(packet.type, packet.length, p);
209						// do another loop to parse any leftover record
210						continue;
211					} else {
212						// not enough. grab the data and park it.
213						stream.readBytes(p, p.length, stream.bytesAvailable);
214						_packetQueue.push(packet);
215						continue;
216					}
217				}
218
219
220				var type:uint = stream.readByte();
221				var ver:uint = stream.readShort();
222				var length:uint = stream.readShort();
223				if (length>16384+2048) { // support compression and encryption overhead.
224					throw new TLSError("Excessive TLS Record length: "+length, TLSError.record_overflow);
225				}
226				// Can pretty much assume that if I'm here, I've got a default config, so let's use it.
227				if (ver != _securityParameters.version ) {
228					throw new TLSError("Unsupported TLS version: "+ver.toString(16), TLSError.protocol_version);
229				}
230
231				p = new ByteArray;
232				var actualLength:uint = Math.min(stream.bytesAvailable, length);
233				stream.readBytes(p, 0, actualLength);
234				if (actualLength == length) {
235					parseOneRecord(type, length, p);
236				} else {
237					_packetQueue.push({type:type, length:length, data:p});
238				}
239			}
240		}
241		
242		
243		// Protocol handler map, provides a mapping of protocol types to individual packet handlers
244		private var protocolHandlers:Object = { 23 : parseApplicationData, // PROTOCOL_APPLICATION_DATA
245											  22 : parseHandshake, // PROTOCOL_HANDSHAKE
246											  21 : parseAlert, // PROTOCOL_ALERT
247											  20 : parseChangeCipherSpec }; // PROTOCOL_CHANGE_CIPHER_SPEC
248		
249		/**
250		 * Modified to support the notion of a handler map(see above ), since it makes for better clarity (IMHO of course).
251		 */
252		private function parseOneRecord(type:uint, length:uint, p:ByteArray):void {
253			p = _currentReadState.decrypt(type, length, p);
254			if (p.length>16384) { 
255				throw new TLSError("Excessive Decrypted TLS Record length: "+p.length, TLSError.record_overflow);
256			}
257			if (protocolHandlers.hasOwnProperty( type )) {
258				while( p != null)
259					p = protocolHandlers[ type ]( p );
260			} else {
261				throw new TLSError("Unsupported TLS Record Content Type: "+type.toString( 16 ), TLSError.unexpected_message);
262			}
263		}
264		
265		///////// handshake handling
266		// session identifier
267		// peer certificate
268		// compression method
269		// cipher spec
270		// master secret
271		// is resumable
272		private static const HANDSHAKE_HELLO_REQUEST:uint = 0;
273		private static const HANDSHAKE_CLIENT_HELLO:uint = 1;
274		private static const HANDSHAKE_SERVER_HELLO:uint = 2;
275		private static const HANDSHAKE_CERTIFICATE:uint = 11;
276		private static const HANDSHAKE_SERVER_KEY_EXCHANGE:uint = 12;
277		private static const HANDSHAKE_CERTIFICATE_REQUEST:uint = 13;
278		private static const HANDSHAKE_HELLO_DONE:uint = 14;
279		private static const HANDSHAKE_CERTIFICATE_VERIFY:uint = 15;
280		private static const HANDSHAKE_CLIENT_KEY_EXCHANGE:uint = 16;
281		private static const HANDSHAKE_FINISHED:uint = 20;
282
283		// Server handshake handler map
284		private var handshakeHandlersServer:Object = { 0 : notifyStateError, // HANDSHAKE_HELLO_REQUEST
285													   1 : parseHandshakeClientHello, // HANDSHAKE_CLIENT_HELLO
286													   2 : notifyStateError, // HANDSHAKE_SERVER_HELLO 
287													   11 : loadCertificates, // HANDSHAKE_CERTIFICATE
288													   12 : notifyStateError, // HANDSHAKE_SERVER_KEY_EXCHANGE
289													   13 : notifyStateError, // HANDSHAKE_CERTIFICATE_REQUEST
290													   14 : notifyStateError, // HANDSHAKE_HELLO_DONE
291													   15 : notifyStateError, // HANDSHAKE_CERTIFICATE_VERIFY
292													   16 : parseHandshakeClientKeyExchange, // HANDSHAKE_CLIENT_KEY_EXCHANGE
293													   20 : verifyHandshake // HANDSHAKE_FINISHED													   
294														};
295														
296		// Client handshake handler map										
297		private var handshakeHandlersClient:Object = { 0 : parseHandshakeHello, // HANDSHAKE_HELLO_REQUEST
298													   1 : notifyStateError, // HANDSHAKE_CLIENT_HELLO
299													   2 : parseHandshakeServerHello, // HANDSHAKE_SERVER_HELLO
300													   11 : loadCertificates, // HANDSHAKE_CERTIFICATE
301													   12 : parseServerKeyExchange, // HANDSHAKE_SERVER_KEY_EXCHANGE
302													   13 : setStateRespondWithCertificate, // HANDSHAKE_CERTIFICATE
303													   14 : sendClientAck, // HANDSHAKE_HELLO_DONE  
304													   15 : notifyStateError, // HANDSHAKE_CERTIFICATE_VERIFY
305													   16 : notifyStateError, // HANDSHAKE_CLIENT_KEY_EXCHANGE
306													   20 : verifyHandshake // HANDSHAKE_FINISHED
307														};
308		private var _entityHandshakeHandlers:Object;
309		private var _handshakeCanContinue:Boolean = true; // For handling cases where I might need to pause processing during a handshake (cert issues, etc.).
310		private var _handshakeQueue:Array = [];
311		/**
312		 * The handshake is always started by the client.
313		 */
314		private function startHandshake():void {
315			_state = STATE_NEGOTIATING;
316			// reset some other handshake state. XXX
317			sendClientHello();
318		}
319		
320		/**
321		 * Handle the incoming handshake packet.
322		 * 
323		 */
324		private function parseHandshake(p:ByteArray):ByteArray {
325			
326			if (p.length<4) {
327				trace("Handshake packet is way too short. bailing.");
328				return null;
329			}
330
331			p.position = 0;
332			
333			var rec:ByteArray = p;
334			var type:uint = rec.readUnsignedByte();
335			var tmp:uint = rec.readUnsignedByte();
336			var length:uint = (tmp<<16) | rec.readUnsignedShort();
337			if (length+4>p.length) {
338				// partial read.
339				trace("Handshake packet is incomplete. bailing.");
340				return null;
341			}
342
343			// we need to copy the record, to have a valid FINISHED exchange.
344			if (type!=HANDSHAKE_FINISHED) { 
345				_handshakePayloads.writeBytes(p, 0, length+4);
346			} 
347			
348			// Surf the handler map and find the right handler for this handshake packet type. 
349			// I modified the individual handlers so they encapsulate all possible knowledge 
350			// about the incoming packet type, so no previous handling or massaging of the data 
351			// is required, as was the case using the switch statement. BP
352			if (_entityHandshakeHandlers.hasOwnProperty( type )) {
353				if (_entityHandshakeHandlers[ type ] is Function) 
354					_entityHandshakeHandlers[ type ]( rec );
355			} else {
356				throw new TLSError( "Unimplemented or unknown handshake type!", TLSError.internal_error );
357			}
358			
359			// Get set up for the next packet.
360			if (length+4<p.length) {
361				var n:ByteArray = new ByteArray;
362				n.writeBytes(p,length+4, p.length-(length+4));
363				return n;
364			} else {
365				return null;
366			}
367		}
368		
369		/**
370		 * Throw an error when the detected handshake state isn't a valid state for the given entity type (client vs. server, etc. ).
371		 * This really should abort the handshake, since there's no case in which a server should EVER be confused about the type of entity it is. BP
372		 */
373		private function notifyStateError( rec:ByteArray ) : void {
374			throw new TLSError( "Invalid handshake state for a TLS Entity type of " + _entity, TLSError.internal_error ); 
375		}
376		
377		/**
378		 * two unimplemented functions
379		 */
380		private function parseClientKeyExchange( rec:ByteArray ) : void {
381			throw new TLSError( "ClientKeyExchange is currently unimplemented!", TLSError.internal_error );
382		}
383		
384		private function parseServerKeyExchange( rec:ByteArray ) : void {
385			throw new TLSError( "ServerKeyExchange is currently unimplemented!", TLSError.internal_error );
386		}
387		
388		/**
389		 * Test the server's Finished message for validity against the data we know about. Only slightly rewritten. BP
390		 */
391		private function verifyHandshake( rec:ByteArray):void {
392			// Get the Finished message
393			var verifyData:ByteArray = new ByteArray;
394			// This, in the vain hope that noboby is using SSL 2 anymore
395			if (_securityParameters.version == SSLSecurityParameters.PROTOCOL_VERSION) {
396				rec.readBytes(verifyData, 0, 36); // length should be (in fact, better be) 16 + 20 (md5-size + sha1-size)
397			} else { // presuming TLS
398				rec.readBytes(verifyData, 0, 12);
399			}
400			
401			var data:ByteArray = _securityParameters.computeVerifyData(1-_entity, _handshakePayloads);
402
403			if (ArrayUtil.equals(verifyData, data)) {
404				_state = STATE_READY;
405				dispatchEvent(new TLSEvent(TLSEvent.READY));
406			} else {
407				throw new TLSError("Invalid Finished mac.", TLSError.bad_record_mac);
408			}
409		}
410
411		// enforceClient/enforceServer removed in favor of state-driven function maps
412
413		/**
414		 * Handle a HANDSHAKE_HELLO
415		 */
416		private function parseHandshakeHello( rec:ByteArray ) : void {
417			if (_state != STATE_READY) {
418				trace("Received an HELLO_REQUEST before being in state READY. ignoring.");
419				return;
420			}
421			_handshakePayloads = new ByteArray;
422			startHandshake();
423		}
424		
425		/**
426		 * Handle a HANDSHAKE_CLIENT_KEY_EXCHANGE
427		 */
428		private function parseHandshakeClientKeyExchange(rec:ByteArray):void {
429			if (_securityParameters.useRSA) {
430				// skip 2 bytes for length.
431				var len:uint = rec.readShort();
432				var cipher:ByteArray = new ByteArray;
433				rec.readBytes(cipher, 0, len);
434				var preMasterSecret:ByteArray = new ByteArray;
435				_config.privateKey.decrypt(cipher, preMasterSecret, len);
436				_securityParameters.setPreMasterSecret(preMasterSecret);
437				
438				// now is a good time to get our pending states
439				var o:Object = _securityParameters.getConnectionStates();
440				_pendingReadState = o.read;
441				_pendingWriteState = o.write;
442				
443			} else {
444				throw new TLSError("parseHandshakeClientKeyExchange not implemented for DH modes.", TLSError.internal_error);
445			}
446			
447		}
448		
449		/** 
450		 * Handle HANDSHAKE_SERVER_HELLO - client-side
451		 */
452		private function parseHandshakeServerHello( rec:IDataInput ) : void {
453			
454			var ver:uint = rec.readShort(); 
455			if (ver != _securityParameters.version) {
456				throw new TLSError("Unsupported TLS version: "+ver.toString(16), TLSError.protocol_version);
457			}			
458			var random:ByteArray = new ByteArray;
459			rec.readBytes(random, 0, 32);
460			var session_length:uint = rec.readByte();
461			var session:ByteArray = new ByteArray;
462			if (session_length > 0) {
463				// some implementations don't assign a session ID
464				rec.readBytes(session, 0, session_length);
465			}
466			
467			_securityParameters.setCipher(rec.readShort()); 
468			_securityParameters.setCompression(rec.readByte());
469			_securityParameters.setServerRandom(random);
470		}
471		
472		/**
473		 *  Handle HANDSHAKE_CLIENT_HELLO - server side
474		 */
475		private function parseHandshakeClientHello( rec:IDataInput ) : void {
476			var ret:Object;
477			var ver:uint = rec.readShort(); 
478			if (ver != _securityParameters.version) {
479				throw new TLSError("Unsupported TLS version: "+ver.toString(16), TLSError.protocol_version);
480			}
481			
482			var random:ByteArray = new ByteArray;
483			rec.readBytes(random, 0, 32);
484			var session_length:uint = rec.readByte();
485			var session:ByteArray = new ByteArray;
486			if (session_length > 0) {
487				// some implementations don't assign a session ID
488				rec.readBytes(session, 0, session_length);
489			}
490			var suites:Array = [];
491			
492			var suites_length:uint = rec.readShort();
493			for (var i:uint=0;i<suites_length/2;i++) {
494				suites.push(rec.readShort());
495			}
496		
497			var compressions:Array = [];
498			
499			var comp_length:uint = rec.readByte();
500			for (i=0;i<comp_length;i++) {
501				compressions.push(rec.readByte());
502			}
503
504			ret = {random:random, session:session, suites:suites, compressions:compressions};
505			
506			var sofar:uint = 2+32+1+session_length+2+suites_length+1+comp_length;
507			var extensions:Array = [];
508			if (sofar<length) {
509				// we have extensions. great.
510				var ext_total_length:uint = rec.readShort();
511				while (ext_total_length>0) {
512					var ext_type:uint = rec.readShort();
513					var ext_length:uint = rec.readShort();
514					var ext_data:ByteArray = new ByteArray;
515					rec.readBytes(ext_data, 0, ext_length);
516					ext_total_length -= 4+ext_length;
517					extensions.push({type:ext_type, length:ext_length, data:ext_data});
518				}
519			}
520			ret.ext = extensions;
521			
522			sendServerHello(ret);
523			sendCertificate();
524			// TODO: Modify to handle case of requesting a certificate from the client, for "client authentication", 
525			// and testing purposes, will probably never actually need it.
526			sendServerHelloDone();
527		}
528		
529		private function sendClientHello():void {
530			var rec:ByteArray = new ByteArray;
531			// version - modified to support version attribute from ISecurityParameters
532			rec.writeShort(_securityParameters.version);  
533			// random
534			var prng:Random = new Random;
535			var clientRandom:ByteArray = new ByteArray;
536			prng.nextBytes(clientRandom, 32);
537			_securityParameters.setClientRandom(clientRandom);
538			rec.writeBytes(clientRandom,0,32);
539			// session
540			rec.writeByte(32);
541			prng.nextBytes(rec, 32);
542			// Cipher suites
543			var cs:Array = _config.cipherSuites;
544			rec.writeShort(2* cs.length);
545			for (var i:int=0;i<cs.length;i++) {
546				rec.writeShort(cs[i]);
547			}
548			// Compression
549			cs = _config.compressions;
550			rec.writeByte(cs.length);
551			for (i=0;i<cs.length;i++) {
552				rec.writeByte(cs[i]);
553			}
554			// no extensions, yet.
555			rec.position = 0;
556			sendHandshake(HANDSHAKE_CLIENT_HELLO, rec.length, rec);
557		}
558		
559		private function findMatch(a1:Array, a2:Array):int {
560			for (var i:int=0;i<a1.length;i++) {
561				var e:uint = a1[i];
562				if (a2.indexOf(e)>-1) {
563					return e;
564				}
565			}
566			return -1;
567		}
568		
569		private function sendServerHello(v:Object):void {
570			var cipher:int = findMatch(_config.cipherSuites, v.suites);
571			if (cipher == -1) {
572				throw new TLSError("No compatible cipher found.", TLSError.handshake_failure);
573			}
574			_securityParameters.setCipher(cipher);
575			
576			var comp:int = findMatch(_config.compressions, v.compressions);
577			if (comp == 01) {
578				throw new TLSError("No compatible compression method found.", TLSError.handshake_failure);
579			}
580			_securityParameters.setCompression(comp);
581			_securityParameters.setClientRandom(v.random);
582
583
584			var rec:ByteArray = new ByteArray;
585			rec.writeShort(_securityParameters.version);
586			var prng:Random = new Random;
587			var serverRandom:ByteArray = new ByteArray;
588			prng.nextBytes(serverRandom, 32);
589			_securityParameters.setServerRandom(serverRandom);
590			rec.writeBytes(serverRandom,0,32);
591			// session
592			rec.writeByte(32);
593			prng.nextBytes(rec, 32);
594			// Cipher suite
595			rec.writeShort(v.suites[0]);
596			// Compression
597			rec.writeByte(v.compressions[0]);
598			rec.position = 0;
599			sendHandshake(HANDSHAKE_SERVER_HELLO, rec.length, rec);
600		}
601		
602		private var sendClientCert:Boolean = false;
603		private function setStateRespondWithCertificate( r:ByteArray = null) : void {
604			sendClientCert = true;
605		}
606		
607		private function sendCertificate( r:ByteArray = null ):void {
608			var cert:ByteArray = _config.certificate;
609			var len:uint;
610			var len2:uint;
611			var rec:ByteArray = new ByteArray;
612			// Look for a certficate chain, if we have one, send it, if we don't, send an empty record.
613			if (cert != null) { 
614				len = cert.length;
615				len2 = cert.length + 3;
616				rec.writeByte(len2>>16);
617				rec.writeShort(len2&65535);
618				rec.writeByte(len>>16);
619				rec.writeShort(len&65535);
620				rec.writeBytes(cert);
621			} else {
622				rec.writeShort( 0 );
623				rec.writeByte( 0 );
624			}
625			rec.position = 0;
626			sendHandshake(HANDSHAKE_CERTIFICATE, rec.length, rec);
627		}
628		
629		private function sendCertificateVerify():void {
630			var rec:ByteArray = new ByteArray();
631			// Encrypt the handshake payloads here
632			var data:ByteArray = _securityParameters.computeCertificateVerify(_entity, _handshakePayloads);
633			data.position=0;
634			sendHandshake(HANDSHAKE_CERTIFICATE_VERIFY, data.length, data);
635		}
636		
637		private function sendServerHelloDone():void {
638			var rec:ByteArray = new ByteArray;
639			sendHandshake(HANDSHAKE_HELLO_DONE, rec.length, rec);
640		}
641		
642		private function sendClientKeyExchange():void {
643			if (_securityParameters.useRSA) {
644				var p:ByteArray = new ByteArray;
645				p.writeShort(_securityParameters.version);
646				var prng:Random = new Random;
647				prng.nextBytes(p, 46);
648				p.position = 0;
649
650				var preMasterSecret:ByteArray = new ByteArray;
651				preMasterSecret.writeBytes(p, 0, p.length);
652				preMasterSecret.position = 0;
653				_securityParameters.setPreMasterSecret(preMasterSecret);
654				 			
655				var enc_key:ByteArray = new ByteArray;
656				_otherCertificate.getPublicKey().encrypt(preMasterSecret, enc_key, preMasterSecret.length); 
657				
658				enc_key.position = 0;
659				var rec:ByteArray = new ByteArray;
660				
661				// TLS requires the size of the premaster key be sent BUT
662				// SSL 3.0 does not
663				if (_securityParameters.version > 0x0300) { 
664					rec.writeShort(enc_key.length);
665				}
666				rec.writeBytes(enc_key, 0, enc_key.length);
667				
668				rec.position=0;
669				
670				sendHandshake(HANDSHAKE_CLIENT_KEY_EXCHANGE, rec.length, rec);
671				
672				// now is a good time to get our pending states
673				var o:Object = _securityParameters.getConnectionStates();
674				_pendingReadState = o.read;
675				_pendingWriteState = o.write;
676				
677			} else {
678				throw new TLSError("Non-RSA Client Key Exchange not implemented.", TLSError.internal_error);
679			}
680		}
681		private function sendFinished():void {
682			var data:ByteArray = _securityParameters.computeVerifyData(_entity, _handshakePayloads);
683			data.position=0;
684			sendHandshake(HANDSHAKE_FINISHED, data.length, data);
685		}
686		
687		private function sendHandshake(type:uint, len:uint, payload:IDataInput):void {
688			var rec:ByteArray = new ByteArray;
689			rec.writeByte(type);
690			rec.writeByte(0);
691			rec.writeShort(len);
692			payload.readBytes(rec, rec.position, len);
693			_handshakePayloads.writeBytes(rec, 0, rec.length);
694			sendRecord(PROTOCOL_HANDSHAKE, rec);
695		}
696		
697		private function sendChangeCipherSpec():void {
698			var rec:ByteArray = new ByteArray;
699			rec[0] = 1;
700			sendRecord(PROTOCOL_CHANGE_CIPHER_SPEC, rec);
701			
702			// right after, switch the cipher for writing.
703			_currentWriteState = _pendingWriteState;
704			_pendingWriteState = null;	
705		}
706		
707		public function sendApplicationData(data:ByteArray, offset:uint=0, length:uint=0):void {
708			var rec:ByteArray = new ByteArray;
709			var len:uint = length;
710			// BIG FAT WARNING: Patch from Arlen Cuss ALA As3crypto group on Google code. 
711			// This addresses data overflow issues when the packet size hits the max length boundary.
712			if (len == 0) len = data.length;  
713			while (len>16384) {
714				rec.position = 0;
715				rec.writeBytes(data, offset, 16384);
716				rec.position = 0;
717				sendRecord(PROTOCOL_APPLICATION_DATA, rec);
718				offset += 16384;
719				len -= 16384;
720			}
721			rec.position = 0;
722			rec.writeBytes(data, offset, len);
723			// trace("Data I'm sending..." + Hex.fromArray( data ));
724			rec.position = 0;
725			sendRecord(PROTOCOL_APPLICATION_DATA, rec);
726		}
727		private function sendRecord(type:uint, payload:ByteArray):void {
728			// encrypt
729			payload = _currentWriteState.encrypt(type, payload);
730			
731			_oStream.writeByte(type);
732			_oStream.writeShort(_securityParameters.version);
733			_oStream.writeShort(payload.length);
734			_oStream.writeBytes(payload, 0, payload.length);
735			
736			scheduleWrite();
737		}
738		
739		private var _writeScheduler:uint;
740		private function scheduleWrite():void {
741			if (_writeScheduler!=0) return;
742			_writeScheduler = setTimeout(commitWrite, 0);
743		}
744		private function commitWrite():void {
745			clearTimeout(_writeScheduler);
746			_writeScheduler = 0;
747			if (_state != STATE_CLOSED) {
748				dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA));
749			}
750		}
751		
752		private function sendClientAck( rec:ByteArray ):void {
753			if ( _handshakeCanContinue ) {
754				// If I have a pending cert request, send it
755				if (sendClientCert)
756					sendCertificate();
757				// send a client key exchange
758				sendClientKeyExchange();
759				// Send the certificate verify, if we have one
760				if (_config.certificate != null)
761					sendCertificateVerify();
762				// send a change cipher spec
763				sendChangeCipherSpec();
764				// send a finished
765				sendFinished();
766			}
767		}
768
769		/**
770		 * Vaguely gross function that parses a RSA key out of a certificate.
771		 * 
772		 * As long as that certificate looks just the way we expect it to.
773		 * 
774		 */
775		private function loadCertificates( rec:ByteArray ):void {
776			var tmp:uint = rec.readByte();
777			var certs_len:uint = (tmp<<16) | rec.readShort();
778			var certs:Array = [];
779			
780			while (certs_len>0) {
781				tmp = rec.readByte();
782				var cert_len:uint = (tmp<<16) | rec.readShort();
783				var cert:ByteArray = new ByteArray;
784				rec.readBytes(cert, 0, cert_len);
785				certs.push(cert);
786				certs_len -= 3 + cert_len;
787			}
788			
789			var firstCert:X509Certificate = null;
790			for (var i:int=0;i<certs.length;i++) {
791				var x509:X509Certificate = new X509Certificate(certs[i]);
792				_store.addCertificate(x509);
793				if (firstCert==null) {
794					firstCert = x509;
795				}
796			}
797
798			
799			// Test first for trust override parameters
800			// This nice trust override stuff comes from Joey Parrish via As3crypto forums
801			var certTrusted:Boolean;
802			if (_config.trustAllCertificates) {
803				certTrusted = true; // Blatantly trust everything
804			} else if (_config.trustSelfSignedCertificates ) {
805				// Self-signed certs
806				certTrusted = firstCert.isSelfSigned(new Date); 
807			} else {
808				// Certs with a signer in the CA store - realistically, I should setup an event chain to handle this
809				certTrusted = firstCert.isSigned(_store, _config.CAStore );
810			}
811	
812			// Good so far
813			if (certTrusted) {							
814				// ok, that's encouraging. now for the hostname match.
815				if (_otherIdentity==null || _config.ignoreCommonNameMismatch ) {
816					// we don't care who we're talking with. groovy.
817					_otherCertificate = firstCert;
818				} else {
819					// use regex to handle wildcard certs
820					var commonName:String = firstCert.getCommonName();
821					// replace all regex special characters with escaped version, except for asterisk
822					// replace the asterisk with a regex sequence to match one or more non-dot characters
823					var commonNameRegex:RegExp = new RegExp( commonName.replace(/[\^\\\-$.[\]|()?+{}]/g, "\\$&").replace(/\*/g, "[^.]+"), "gi");
824					if (commonNameRegex.exec(_otherIdentity)) {
825						_otherCertificate = firstCert;
826					} else {
827						if (_config.promptUserForAcceptCert ) {
828							_handshakeCanContinue = false;
829							dispatchEvent( new TLSEvent( TLSEvent.PROMPT_ACCEPT_CERT ));
830						} else {
831					 		throw new TLSError("Invalid common name: "+firstCert.getCommonName()+", expected "+_otherIdentity, TLSError.bad_certificate);
832					 	}
833					}
834				}
835			
836			} else {
837				// Let's ask the user if we can accept this cert. I'm not certain of the behaviour in case of timeouts, 
838				// so I probably need to handle the case by killing and restarting the connection rather than continuing if it becomes 
839				// an issue. We shall see. BP
840				if (_config.promptUserForAcceptCert) {
841					_handshakeCanContinue = false;
842					dispatchEvent( new TLSEvent( TLSEvent.PROMPT_ACCEPT_CERT ));
843				} else {
844					// Cannot continue, die.
845					throw new TLSError("Cannot verify certificate", TLSError.bad_certificate);
846				}
847			}
848		}
849		
850		// Accept the peer cert, and keep going
851		public function acceptPeerCertificate() : void {
852			_handshakeCanContinue = true;
853			sendClientAck( null );
854		}
855		
856		// Step off biotch! No trust for you!
857		public function rejectPeerCertificate() : void {
858			throw new TLSError("Peer certificate not accepted!", TLSError.bad_certificate);
859		}
860		
861		
862		private function parseAlert(p:ByteArray):void {
863			//throw new Error("Alert not implemented.");
864			// 7.2
865			trace("GOT ALERT! type="+p[1]);
866			close();
867		}
868		private function parseChangeCipherSpec(p:ByteArray):void {
869			p.readUnsignedByte();
870			if (_pendingReadState==null) {
871				throw new TLSError("Not ready to Change Cipher Spec, damnit.", TLSError.unexpected_message);
872			}
873			_currentReadState = _pendingReadState;
874			_pendingReadState = null;
875			// 7.1
876		}
877		private function parseApplicationData(p:ByteArray):void {
878			if (_state != STATE_READY) {
879				throw new TLSError("Too soon for data!", TLSError.unexpected_message);
880				return;
881			}
882			dispatchEvent(new TLSEvent(TLSEvent.DATA, p));
883		}
884		
885		private function handleTLSError(e:TLSError):void {
886			// basic rules to keep things simple:
887			// - Make a good faith attempt at notifying peers
888			// - TLSErrors are always fatal.
889			// BP: Meh...not always. Common Name mismatches appear to be common on servers. Instead of closing, let's pause, and ask for confirmation 
890			// before we tear the connection down.
891			
892			close(e);
893		}
894	}
895}