4 * A TLS protocol implementation.
5 * See comment below for some details.
6 * Copyright (c) 2007 Henri Torgemane
8 * Patched(heavily) by Bobby Parker (shortwave@gmail.com)
10 * See LICENSE.txt for full license information.
12 package 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;
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;
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")]
36 * The heart of the TLS protocol.
37 * This class can work in server or client mode.
39 * This doesn't fully implement the TLS protocol.
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
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)
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?
56 public class TLSEngine extends EventDispatcher {
58 public static const SERVER:uint = 0;
59 public static const CLIENT:uint = 1;
60 public var protocol_version:uint;
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;
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.
75 private var _entity:uint; // SERVER | CLIENT
76 private var _config:TLSConfig;
78 private var _state:uint;
80 private var _securityParameters:ISecurityParameters;
82 private var _currentReadState:IConnectionState;
83 private var _currentWriteState:IConnectionState;
84 private var _pendingReadState:IConnectionState;
85 private var _pendingWriteState:IConnectionState;
87 private var _handshakePayloads:ByteArray;
88 private var _handshakeRecords:ByteArray; // For client-side certificate verify
90 private var _iStream:IDataInput;
91 private var _oStream:IDataOutput;
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;
98 public function get peerCertificate() : X509Certificate {
99 return _otherCertificate;
101 // If this isn't null, we expect this identity to be found in the Cert's Subject CN.
102 private var _otherIdentity:String;
104 // The client-side cert
105 private var _myCertficate:X509Certificate;
107 private var _myIdentity:String;
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.
117 function TLSEngine(config:TLSConfig, iStream:IDataInput, oStream:IDataOutput, otherIdentity:String = null) {
118 _entity = config.entity;
122 _otherIdentity = otherIdentity;
126 // Pick the right set of callbacks
127 _entityHandshakeHandlers = _entity == CLIENT ? handshakeHandlersClient : handshakeHandlersServer;
129 // setting up new security parameters needs to be controlled by...something.
130 if (_config.version == SSLSecurityParameters.PROTOCOL_VERSION) {
131 _securityParameters = new SSLSecurityParameters(_entity);
133 _securityParameters = new TLSSecurityParameters(_entity, _config.certificate, _config.privateKey);
135 protocol_version = _config.version;
137 // So this...why is it here, other than to preclude a possible null pointer situation?
138 var states:Object = _securityParameters.getConnectionStates();
140 _currentReadState = states.read;
141 _currentWriteState = states.write;
143 _handshakePayloads = new ByteArray;
145 _store = new X509CertificateCollection;
149 * This starts the TLS negotiation for a TLS Client.
151 * This is a no-op for a TLS Server.
154 public function start():void {
155 if (_entity == CLIENT) {
158 } catch (e:TLSError) {
165 public function dataAvailable(e:* = null):void {
166 if (_state == STATE_CLOSED) return; // ignore
168 parseRecord(_iStream);
169 } catch (e:TLSError) {
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
181 rec[1] = TLSError.user_canceled;
182 sendRecord(PROTOCOL_ALERT, rec);
186 rec[1] = TLSError.close_notify;
189 trace("TLSEngine shutdown triggered by "+e);
191 sendRecord(PROTOCOL_ALERT, rec);
193 _state = STATE_CLOSED;
194 dispatchEvent(new Event(Event.CLOSE));
197 private var _packetQueue:Array = [];
198 private function parseRecord(stream:IDataInput):void {
200 while(_state!=STATE_CLOSED && stream.bytesAvailable>4) {
202 if (_packetQueue.length>0) {
203 var packet:Object = _packetQueue.shift();
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
212 // not enough. grab the data and park it.
213 stream.readBytes(p, p.length, stream.bytesAvailable);
214 _packetQueue.push(packet);
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);
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);
232 var actualLength:uint = Math.min(stream.bytesAvailable, length);
233 stream.readBytes(p, 0, actualLength);
234 if (actualLength == length) {
235 parseOneRecord(type, length, p);
237 _packetQueue.push({type:type, length:length, data:p});
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
250 * Modified to support the notion of a handler map(see above ), since it makes for better clarity (IMHO of course).
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);
257 if (protocolHandlers.hasOwnProperty( type )) {
259 p = protocolHandlers[ type ]( p );
261 throw new TLSError("Unsupported TLS Record Content Type: "+type.toString( 16 ), TLSError.unexpected_message);
265 ///////// handshake handling
266 // session identifier
268 // compression method
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;
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
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
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 = [];
312 * The handshake is always started by the client.
314 private function startHandshake():void {
315 _state = STATE_NEGOTIATING;
316 // reset some other handshake state. XXX
321 * Handle the incoming handshake packet.
324 private function parseHandshake(p:ByteArray):ByteArray {
327 trace("Handshake packet is way too short. bailing.");
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) {
339 trace("Handshake packet is incomplete. bailing.");
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);
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 );
356 throw new TLSError( "Unimplemented or unknown handshake type!", TLSError.internal_error );
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));
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
373 private function notifyStateError( rec:ByteArray ) : void {
374 throw new TLSError( "Invalid handshake state for a TLS Entity type of " + _entity, TLSError.internal_error );
378 * two unimplemented functions
380 private function parseClientKeyExchange( rec:ByteArray ) : void {
381 throw new TLSError( "ClientKeyExchange is currently unimplemented!", TLSError.internal_error );
384 private function parseServerKeyExchange( rec:ByteArray ) : void {
385 throw new TLSError( "ServerKeyExchange is currently unimplemented!", TLSError.internal_error );
389 * Test the server's Finished message for validity against the data we know about. Only slightly rewritten. BP
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);
401 var data:ByteArray = _securityParameters.computeVerifyData(1-_entity, _handshakePayloads);
403 if (ArrayUtil.equals(verifyData, data)) {
404 _state = STATE_READY;
405 dispatchEvent(new TLSEvent(TLSEvent.READY));
407 throw new TLSError("Invalid Finished mac.", TLSError.bad_record_mac);
411 // enforceClient/enforceServer removed in favor of state-driven function maps
414 * Handle a HANDSHAKE_HELLO
416 private function parseHandshakeHello( rec:ByteArray ) : void {
417 if (_state != STATE_READY) {
418 trace("Received an HELLO_REQUEST before being in state READY. ignoring.");
421 _handshakePayloads = new ByteArray;
426 * Handle a HANDSHAKE_CLIENT_KEY_EXCHANGE
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);
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;
444 throw new TLSError("parseHandshakeClientKeyExchange not implemented for DH modes.", TLSError.internal_error);
450 * Handle HANDSHAKE_SERVER_HELLO - client-side
452 private function parseHandshakeServerHello( rec:IDataInput ) : void {
454 var ver:uint = rec.readShort();
455 if (ver != _securityParameters.version) {
456 throw new TLSError("Unsupported TLS version: "+ver.toString(16), TLSError.protocol_version);
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);
467 _securityParameters.setCipher(rec.readShort());
468 _securityParameters.setCompression(rec.readByte());
469 _securityParameters.setServerRandom(random);
473 * Handle HANDSHAKE_CLIENT_HELLO - server side
475 private function parseHandshakeClientHello( rec:IDataInput ) : void {
477 var ver:uint = rec.readShort();
478 if (ver != _securityParameters.version) {
479 throw new TLSError("Unsupported TLS version: "+ver.toString(16), TLSError.protocol_version);
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);
490 var suites:Array = [];
492 var suites_length:uint = rec.readShort();
493 for (var i:uint=0;i<suites_length/2;i++) {
494 suites.push(rec.readShort());
497 var compressions:Array = [];
499 var comp_length:uint = rec.readByte();
500 for (i=0;i<comp_length;i++) {
501 compressions.push(rec.readByte());
504 ret = {random:random, session:session, suites:suites, compressions:compressions};
506 var sofar:uint = 2+32+1+session_length+2+suites_length+1+comp_length;
507 var extensions:Array = [];
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});
520 ret.ext = extensions;
522 sendServerHello(ret);
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();
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);
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);
541 prng.nextBytes(rec, 32);
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]);
549 cs = _config.compressions;
550 rec.writeByte(cs.length);
551 for (i=0;i<cs.length;i++) {
552 rec.writeByte(cs[i]);
554 // no extensions, yet.
556 sendHandshake(HANDSHAKE_CLIENT_HELLO, rec.length, rec);
559 private function findMatch(a1:Array, a2:Array):int {
560 for (var i:int=0;i<a1.length;i++) {
562 if (a2.indexOf(e)>-1) {
569 private function sendServerHello(v:Object):void {
570 var cipher:int = findMatch(_config.cipherSuites, v.suites);
572 throw new TLSError("No compatible cipher found.", TLSError.handshake_failure);
574 _securityParameters.setCipher(cipher);
576 var comp:int = findMatch(_config.compressions, v.compressions);
578 throw new TLSError("No compatible compression method found.", TLSError.handshake_failure);
580 _securityParameters.setCompression(comp);
581 _securityParameters.setClientRandom(v.random);
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);
593 prng.nextBytes(rec, 32);
595 rec.writeShort(v.suites[0]);
597 rec.writeByte(v.compressions[0]);
599 sendHandshake(HANDSHAKE_SERVER_HELLO, rec.length, rec);
602 private var sendClientCert:Boolean = false;
603 private function setStateRespondWithCertificate( r:ByteArray = null) : void {
604 sendClientCert = true;
607 private function sendCertificate( r:ByteArray = null ):void {
608 var cert:ByteArray = _config.certificate;
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.
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);
626 sendHandshake(HANDSHAKE_CERTIFICATE, rec.length, rec);
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);
634 sendHandshake(HANDSHAKE_CERTIFICATE_VERIFY, data.length, data);
637 private function sendServerHelloDone():void {
638 var rec:ByteArray = new ByteArray;
639 sendHandshake(HANDSHAKE_HELLO_DONE, rec.length, rec);
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);
650 var preMasterSecret:ByteArray = new ByteArray;
651 preMasterSecret.writeBytes(p, 0, p.length);
652 preMasterSecret.position = 0;
653 _securityParameters.setPreMasterSecret(preMasterSecret);
655 var enc_key:ByteArray = new ByteArray;
656 _otherCertificate.getPublicKey().encrypt(preMasterSecret, enc_key, preMasterSecret.length);
658 enc_key.position = 0;
659 var rec:ByteArray = new ByteArray;
661 // TLS requires the size of the premaster key be sent BUT
663 if (_securityParameters.version > 0x0300) {
664 rec.writeShort(enc_key.length);
666 rec.writeBytes(enc_key, 0, enc_key.length);
670 sendHandshake(HANDSHAKE_CLIENT_KEY_EXCHANGE, rec.length, rec);
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;
678 throw new TLSError("Non-RSA Client Key Exchange not implemented.", TLSError.internal_error);
681 private function sendFinished():void {
682 var data:ByteArray = _securityParameters.computeVerifyData(_entity, _handshakePayloads);
684 sendHandshake(HANDSHAKE_FINISHED, data.length, data);
687 private function sendHandshake(type:uint, len:uint, payload:IDataInput):void {
688 var rec:ByteArray = new ByteArray;
692 payload.readBytes(rec, rec.position, len);
693 _handshakePayloads.writeBytes(rec, 0, rec.length);
694 sendRecord(PROTOCOL_HANDSHAKE, rec);
697 private function sendChangeCipherSpec():void {
698 var rec:ByteArray = new ByteArray;
700 sendRecord(PROTOCOL_CHANGE_CIPHER_SPEC, rec);
702 // right after, switch the cipher for writing.
703 _currentWriteState = _pendingWriteState;
704 _pendingWriteState = null;
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;
715 rec.writeBytes(data, offset, 16384);
717 sendRecord(PROTOCOL_APPLICATION_DATA, rec);
722 rec.writeBytes(data, offset, len);
723 // trace("Data I'm sending..." + Hex.fromArray( data ));
725 sendRecord(PROTOCOL_APPLICATION_DATA, rec);
727 private function sendRecord(type:uint, payload:ByteArray):void {
729 payload = _currentWriteState.encrypt(type, payload);
731 _oStream.writeByte(type);
732 _oStream.writeShort(_securityParameters.version);
733 _oStream.writeShort(payload.length);
734 _oStream.writeBytes(payload, 0, payload.length);
739 private var _writeScheduler:uint;
740 private function scheduleWrite():void {
741 if (_writeScheduler!=0) return;
742 _writeScheduler = setTimeout(commitWrite, 0);
744 private function commitWrite():void {
745 clearTimeout(_writeScheduler);
747 if (_state != STATE_CLOSED) {
748 dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA));
752 private function sendClientAck( rec:ByteArray ):void {
753 if ( _handshakeCanContinue ) {
754 // If I have a pending cert request, send it
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();
770 * Vaguely gross function that parses a RSA key out of a certificate.
772 * As long as that certificate looks just the way we expect it to.
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 = [];
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);
786 certs_len -= 3 + cert_len;
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) {
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 ) {
806 certTrusted = firstCert.isSelfSigned(new Date);
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 );
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;
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;
827 if (_config.promptUserForAcceptCert ) {
828 _handshakeCanContinue = false;
829 dispatchEvent( new TLSEvent( TLSEvent.PROMPT_ACCEPT_CERT ));
831 throw new TLSError("Invalid common name: "+firstCert.getCommonName()+", expected "+_otherIdentity, TLSError.bad_certificate);
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 ));
844 // Cannot continue, die.
845 throw new TLSError("Cannot verify certificate", TLSError.bad_certificate);
850 // Accept the peer cert, and keep going
851 public function acceptPeerCertificate() : void {
852 _handshakeCanContinue = true;
853 sendClientAck( null );
856 // Step off biotch! No trust for you!
857 public function rejectPeerCertificate() : void {
858 throw new TLSError("Peer certificate not accepted!", TLSError.bad_certificate);
862 private function parseAlert(p:ByteArray):void {
863 //throw new Error("Alert not implemented.");
865 trace("GOT ALERT! type="+p[1]);
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);
873 _currentReadState = _pendingReadState;
874 _pendingReadState = null;
877 private function parseApplicationData(p:ByteArray):void {
878 if (_state != STATE_READY) {
879 throw new TLSError("Too soon for data!", TLSError.unexpected_message);
882 dispatchEvent(new TLSEvent(TLSEvent.DATA, p));
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.