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);
723
rec.writeBytes(data, offset, len);
724
// trace("Data I'm sending..." + Hex.fromArray( data ));
726
sendRecord(PROTOCOL_APPLICATION_DATA, rec);
728
private function sendRecord(type:uint, payload:ByteArray):void {
730
payload = _currentWriteState.encrypt(type, payload);
732
_oStream.writeByte(type);
733
_oStream.writeShort(_securityParameters.version);
734
_oStream.writeShort(payload.length);
735
_oStream.writeBytes(payload, 0, payload.length);
740
private var _writeScheduler:uint;
741
private function scheduleWrite():void {
742
if (_writeScheduler!=0) return;
743
_writeScheduler = setTimeout(commitWrite, 0);
745
private function commitWrite():void {
746
clearTimeout(_writeScheduler);
748
if (_state != STATE_CLOSED) {
749
dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA));
753
private function sendClientAck( rec:ByteArray ):void {
754
if ( _handshakeCanContinue ) {
755
// If I have a pending cert request, send it
758
// send a client key exchange
759
sendClientKeyExchange();
760
// Send the certificate verify, if we have one
761
if (_config.certificate != null)
762
sendCertificateVerify();
763
// send a change cipher spec
764
sendChangeCipherSpec();
771
* Vaguely gross function that parses a RSA key out of a certificate.
773
* As long as that certificate looks just the way we expect it to.
776
private function loadCertificates( rec:ByteArray ):void {
777
var tmp:uint = rec.readByte();
778
var certs_len:uint = (tmp<<16) | rec.readShort();
779
var certs:Array = [];
781
while (certs_len>0) {
782
tmp = rec.readByte();
783
var cert_len:uint = (tmp<<16) | rec.readShort();
784
var cert:ByteArray = new ByteArray;
785
rec.readBytes(cert, 0, cert_len);
787
certs_len -= 3 + cert_len;
790
var firstCert:X509Certificate = null;
791
for (var i:int=0;i<certs.length;i++) {
792
var x509:X509Certificate = new X509Certificate(certs[i]);
793
_store.addCertificate(x509);
794
if (firstCert==null) {
800
// Test first for trust override parameters
801
// This nice trust override stuff comes from Joey Parrish via As3crypto forums
802
var certTrusted:Boolean;
803
if (_config.trustAllCertificates) {
804
certTrusted = true; // Blatantly trust everything
805
} else if (_config.trustSelfSignedCertificates ) {
807
certTrusted = firstCert.isSelfSigned(new Date);
809
// Certs with a signer in the CA store - realistically, I should setup an event chain to handle this
810
certTrusted = firstCert.isSigned(_store, _config.CAStore );
815
// ok, that's encouraging. now for the hostname match.
816
if (_otherIdentity==null || _config.ignoreCommonNameMismatch ) {
817
// we don't care who we're talking with. groovy.
818
_otherCertificate = firstCert;
820
// use regex to handle wildcard certs
821
var commonName:String = firstCert.getCommonName();
822
// replace all regex special characters with escaped version, except for asterisk
823
// replace the asterisk with a regex sequence to match one or more non-dot characters
824
var commonNameRegex:RegExp = new RegExp( commonName.replace(/[\^\\\-$.[\]|()?+{}]/g, "\\$&").replace(/\*/g, "[^.]+"), "gi");
825
if (commonNameRegex.exec(_otherIdentity)) {
826
_otherCertificate = firstCert;
828
if (_config.promptUserForAcceptCert ) {
829
_handshakeCanContinue = false;
830
dispatchEvent( new TLSEvent( TLSEvent.PROMPT_ACCEPT_CERT ));
832
throw new TLSError("Invalid common name: "+firstCert.getCommonName()+", expected "+_otherIdentity, TLSError.bad_certificate);
838
// Let's ask the user if we can accept this cert. I'm not certain of the behaviour in case of timeouts,
839
// so I probably need to handle the case by killing and restarting the connection rather than continuing if it becomes
840
// an issue. We shall see. BP
841
if (_config.promptUserForAcceptCert) {
842
_handshakeCanContinue = false;
843
dispatchEvent( new TLSEvent( TLSEvent.PROMPT_ACCEPT_CERT ));
845
// Cannot continue, die.
846
throw new TLSError("Cannot verify certificate", TLSError.bad_certificate);
851
// Accept the peer cert, and keep going
852
public function acceptPeerCertificate() : void {
853
_handshakeCanContinue = true;
854
sendClientAck( null );
857
// Step off biotch! No trust for you!
858
public function rejectPeerCertificate() : void {
859
throw new TLSError("Peer certificate not accepted!", TLSError.bad_certificate);
863
private function parseAlert(p:ByteArray):void {
864
//throw new Error("Alert not implemented.");
866
trace("GOT ALERT! type="+p[1]);
869
private function parseChangeCipherSpec(p:ByteArray):void {
870
p.readUnsignedByte();
871
if (_pendingReadState==null) {
872
throw new TLSError("Not ready to Change Cipher Spec, damnit.", TLSError.unexpected_message);
874
_currentReadState = _pendingReadState;
875
_pendingReadState = null;
878
private function parseApplicationData(p:ByteArray):void {
879
if (_state != STATE_READY) {
880
throw new TLSError("Too soon for data!", TLSError.unexpected_message);
883
dispatchEvent(new TLSEvent(TLSEvent.DATA, p));
886
private function handleTLSError(e:TLSError):void {
887
// basic rules to keep things simple:
888
// - Make a good faith attempt at notifying peers
889
// - TLSErrors are always fatal.
890
// BP: Meh...not always. Common Name mismatches appear to be common on servers. Instead of closing, let's pause, and ask for confirmation
891
// before we tear the connection down.