From b60df56157ee1fd0bd4938799bac05a62fda91a1 Mon Sep 17 00:00:00 2001 From: lookshe Date: Sat, 14 Mar 2015 20:45:20 +0100 Subject: initial commit from working version --- .../flash-src/com/hurlant/crypto/tls/TLSEngine.as | 895 +++++++++++++++++++++ 1 file changed, 895 insertions(+) create mode 100644 signaling-server/node_modules/socket.io/node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSEngine.as (limited to 'signaling-server/node_modules/socket.io/node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSEngine.as') diff --git a/signaling-server/node_modules/socket.io/node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSEngine.as b/signaling-server/node_modules/socket.io/node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSEngine.as new file mode 100644 index 0000000..72f3941 --- /dev/null +++ b/signaling-server/node_modules/socket.io/node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSEngine.as @@ -0,0 +1,895 @@ +/** + * TLSEngine + * + * A TLS protocol implementation. + * See comment below for some details. + * Copyright (c) 2007 Henri Torgemane + * + * Patched(heavily) by Bobby Parker (shortwave@gmail.com) + * + * See LICENSE.txt for full license information. + */ +package com.hurlant.crypto.tls { + import com.hurlant.crypto.cert.X509Certificate; + import com.hurlant.crypto.cert.X509CertificateCollection; + import com.hurlant.crypto.prng.Random; + import com.hurlant.util.ArrayUtil; + import com.hurlant.util.Hex; + + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.ProgressEvent; + import flash.utils.ByteArray; + import flash.utils.IDataInput; + import flash.utils.IDataOutput; + import flash.utils.clearTimeout; + import flash.utils.setTimeout; + import com.hurlant.crypto.prng.ARC4; + + + [Event(name="close", type="flash.events.Event")] + [Event(name="socketData", type="flash.events.ProgressEvent")] + [Event(name="ready", type="com.hurlant.crypto.tls.TLSEvent")] + [Event(name="data", type="com.hurlant.crypto.tls.TLSEvent")] + + /** + * The heart of the TLS protocol. + * This class can work in server or client mode. + * + * This doesn't fully implement the TLS protocol. + * + * Things missing that I'd like to add: + * - support for client-side certificates + * - general code clean-up to make sure we don't have gaping securite holes + * + * Things that aren't there that I won't add: + * - support for "export" cypher suites (deprecated in later TLS versions) + * - support for "anon" cypher suites (deprecated in later TLS versions) + * + * Things that I'm unsure about adding later: + * - compression. Compressing encrypted streams is barely worth the CPU cycles. + * - diffie-hellman based key exchange mechanisms. Nifty, but would we miss it? + * + * @author henri + * + */ + public class TLSEngine extends EventDispatcher { + + public static const SERVER:uint = 0; + public static const CLIENT:uint = 1; + public var protocol_version:uint; + + + + private static const PROTOCOL_HANDSHAKE:uint = 22; + private static const PROTOCOL_ALERT:uint = 21; + private static const PROTOCOL_CHANGE_CIPHER_SPEC:uint = 20; + private static const PROTOCOL_APPLICATION_DATA:uint = 23; + + + private static const STATE_NEW:uint = 0; // brand new. nothing happened yet + private static const STATE_NEGOTIATING:uint = 1; // we're figuring out what to use + private static const STATE_READY:uint = 2; // we're ready for AppData stuff to go over us. + private static const STATE_CLOSED:uint = 3; // we're done done. + + private var _entity:uint; // SERVER | CLIENT + private var _config:TLSConfig; + + private var _state:uint; + + private var _securityParameters:ISecurityParameters; + + private var _currentReadState:IConnectionState; + private var _currentWriteState:IConnectionState; + private var _pendingReadState:IConnectionState; + private var _pendingWriteState:IConnectionState; + + private var _handshakePayloads:ByteArray; + private var _handshakeRecords:ByteArray; // For client-side certificate verify + + private var _iStream:IDataInput; + private var _oStream:IDataOutput; + + // temporary store for X509 certs received by this engine. + private var _store:X509CertificateCollection; + // the main certificate received from the other side. + private var _otherCertificate:X509Certificate; + + public function get peerCertificate() : X509Certificate { + return _otherCertificate; + } + // If this isn't null, we expect this identity to be found in the Cert's Subject CN. + private var _otherIdentity:String; + + // The client-side cert + private var _myCertficate:X509Certificate; + // My Identity + private var _myIdentity:String; + + /** + * + * @param config A TLSConfig instance describing how we're supposed to work + * @param iStream An input stream to read TLS data from + * @param oStream An output stream to write TLS data to + * @param otherIdentity An optional identifier. If set, this will be checked against the Subject CN of the other side's certificate. + * + */ + function TLSEngine(config:TLSConfig, iStream:IDataInput, oStream:IDataOutput, otherIdentity:String = null) { + _entity = config.entity; + _config = config; + _iStream = iStream; + _oStream = oStream; + _otherIdentity = otherIdentity; + + _state = STATE_NEW; + + // Pick the right set of callbacks + _entityHandshakeHandlers = _entity == CLIENT ? handshakeHandlersClient : handshakeHandlersServer; + + // setting up new security parameters needs to be controlled by...something. + if (_config.version == SSLSecurityParameters.PROTOCOL_VERSION) { + _securityParameters = new SSLSecurityParameters(_entity); + } else { + _securityParameters = new TLSSecurityParameters(_entity, _config.certificate, _config.privateKey); + } + protocol_version = _config.version; + + // So this...why is it here, other than to preclude a possible null pointer situation? + var states:Object = _securityParameters.getConnectionStates(); + + _currentReadState = states.read; + _currentWriteState = states.write; + + _handshakePayloads = new ByteArray; + + _store = new X509CertificateCollection; + } + + /** + * This starts the TLS negotiation for a TLS Client. + * + * This is a no-op for a TLS Server. + * + */ + public function start():void { + if (_entity == CLIENT) { + try { + startHandshake(); + } catch (e:TLSError) { + handleTLSError(e); + } + } + } + + + public function dataAvailable(e:* = null):void { + if (_state == STATE_CLOSED) return; // ignore + try { + parseRecord(_iStream); + } catch (e:TLSError) { + handleTLSError(e); + } + } + + public function close(e:TLSError = null):void { + if (_state == STATE_CLOSED) return; // ignore + // ok. send an Alert to let the peer know + var rec:ByteArray = new ByteArray; + if (e==null && _state != STATE_READY) { + // use canceled while handshaking. be nice about it + rec[0] = 1; + rec[1] = TLSError.user_canceled; + sendRecord(PROTOCOL_ALERT, rec); + } + rec[0] = 2; + if (e == null) { + rec[1] = TLSError.close_notify; + } else { + rec[1] = e.errorID; + trace("TLSEngine shutdown triggered by "+e); + } + sendRecord(PROTOCOL_ALERT, rec); + + _state = STATE_CLOSED; + dispatchEvent(new Event(Event.CLOSE)); + } + + private var _packetQueue:Array = []; + private function parseRecord(stream:IDataInput):void { + var p:ByteArray; + while(_state!=STATE_CLOSED && stream.bytesAvailable>4) { + + if (_packetQueue.length>0) { + var packet:Object = _packetQueue.shift(); + p = packet.data; + if (stream.bytesAvailable+p.length>=packet.length) { + // we have a whole packet. put together. + stream.readBytes(p, p.length, packet.length-p.length); + parseOneRecord(packet.type, packet.length, p); + // do another loop to parse any leftover record + continue; + } else { + // not enough. grab the data and park it. + stream.readBytes(p, p.length, stream.bytesAvailable); + _packetQueue.push(packet); + continue; + } + } + + + var type:uint = stream.readByte(); + var ver:uint = stream.readShort(); + var length:uint = stream.readShort(); + if (length>16384+2048) { // support compression and encryption overhead. + throw new TLSError("Excessive TLS Record length: "+length, TLSError.record_overflow); + } + // Can pretty much assume that if I'm here, I've got a default config, so let's use it. + if (ver != _securityParameters.version ) { + throw new TLSError("Unsupported TLS version: "+ver.toString(16), TLSError.protocol_version); + } + + p = new ByteArray; + var actualLength:uint = Math.min(stream.bytesAvailable, length); + stream.readBytes(p, 0, actualLength); + if (actualLength == length) { + parseOneRecord(type, length, p); + } else { + _packetQueue.push({type:type, length:length, data:p}); + } + } + } + + + // Protocol handler map, provides a mapping of protocol types to individual packet handlers + private var protocolHandlers:Object = { 23 : parseApplicationData, // PROTOCOL_APPLICATION_DATA + 22 : parseHandshake, // PROTOCOL_HANDSHAKE + 21 : parseAlert, // PROTOCOL_ALERT + 20 : parseChangeCipherSpec }; // PROTOCOL_CHANGE_CIPHER_SPEC + + /** + * Modified to support the notion of a handler map(see above ), since it makes for better clarity (IMHO of course). + */ + private function parseOneRecord(type:uint, length:uint, p:ByteArray):void { + p = _currentReadState.decrypt(type, length, p); + if (p.length>16384) { + throw new TLSError("Excessive Decrypted TLS Record length: "+p.length, TLSError.record_overflow); + } + if (protocolHandlers.hasOwnProperty( type )) { + while( p != null) + p = protocolHandlers[ type ]( p ); + } else { + throw new TLSError("Unsupported TLS Record Content Type: "+type.toString( 16 ), TLSError.unexpected_message); + } + } + + ///////// handshake handling + // session identifier + // peer certificate + // compression method + // cipher spec + // master secret + // is resumable + private static const HANDSHAKE_HELLO_REQUEST:uint = 0; + private static const HANDSHAKE_CLIENT_HELLO:uint = 1; + private static const HANDSHAKE_SERVER_HELLO:uint = 2; + private static const HANDSHAKE_CERTIFICATE:uint = 11; + private static const HANDSHAKE_SERVER_KEY_EXCHANGE:uint = 12; + private static const HANDSHAKE_CERTIFICATE_REQUEST:uint = 13; + private static const HANDSHAKE_HELLO_DONE:uint = 14; + private static const HANDSHAKE_CERTIFICATE_VERIFY:uint = 15; + private static const HANDSHAKE_CLIENT_KEY_EXCHANGE:uint = 16; + private static const HANDSHAKE_FINISHED:uint = 20; + + // Server handshake handler map + private var handshakeHandlersServer:Object = { 0 : notifyStateError, // HANDSHAKE_HELLO_REQUEST + 1 : parseHandshakeClientHello, // HANDSHAKE_CLIENT_HELLO + 2 : notifyStateError, // HANDSHAKE_SERVER_HELLO + 11 : loadCertificates, // HANDSHAKE_CERTIFICATE + 12 : notifyStateError, // HANDSHAKE_SERVER_KEY_EXCHANGE + 13 : notifyStateError, // HANDSHAKE_CERTIFICATE_REQUEST + 14 : notifyStateError, // HANDSHAKE_HELLO_DONE + 15 : notifyStateError, // HANDSHAKE_CERTIFICATE_VERIFY + 16 : parseHandshakeClientKeyExchange, // HANDSHAKE_CLIENT_KEY_EXCHANGE + 20 : verifyHandshake // HANDSHAKE_FINISHED + }; + + // Client handshake handler map + private var handshakeHandlersClient:Object = { 0 : parseHandshakeHello, // HANDSHAKE_HELLO_REQUEST + 1 : notifyStateError, // HANDSHAKE_CLIENT_HELLO + 2 : parseHandshakeServerHello, // HANDSHAKE_SERVER_HELLO + 11 : loadCertificates, // HANDSHAKE_CERTIFICATE + 12 : parseServerKeyExchange, // HANDSHAKE_SERVER_KEY_EXCHANGE + 13 : setStateRespondWithCertificate, // HANDSHAKE_CERTIFICATE + 14 : sendClientAck, // HANDSHAKE_HELLO_DONE + 15 : notifyStateError, // HANDSHAKE_CERTIFICATE_VERIFY + 16 : notifyStateError, // HANDSHAKE_CLIENT_KEY_EXCHANGE + 20 : verifyHandshake // HANDSHAKE_FINISHED + }; + private var _entityHandshakeHandlers:Object; + private var _handshakeCanContinue:Boolean = true; // For handling cases where I might need to pause processing during a handshake (cert issues, etc.). + private var _handshakeQueue:Array = []; + /** + * The handshake is always started by the client. + */ + private function startHandshake():void { + _state = STATE_NEGOTIATING; + // reset some other handshake state. XXX + sendClientHello(); + } + + /** + * Handle the incoming handshake packet. + * + */ + private function parseHandshake(p:ByteArray):ByteArray { + + if (p.length<4) { + trace("Handshake packet is way too short. bailing."); + return null; + } + + p.position = 0; + + var rec:ByteArray = p; + var type:uint = rec.readUnsignedByte(); + var tmp:uint = rec.readUnsignedByte(); + var length:uint = (tmp<<16) | rec.readUnsignedShort(); + if (length+4>p.length) { + // partial read. + trace("Handshake packet is incomplete. bailing."); + return null; + } + + // we need to copy the record, to have a valid FINISHED exchange. + if (type!=HANDSHAKE_FINISHED) { + _handshakePayloads.writeBytes(p, 0, length+4); + } + + // Surf the handler map and find the right handler for this handshake packet type. + // I modified the individual handlers so they encapsulate all possible knowledge + // about the incoming packet type, so no previous handling or massaging of the data + // is required, as was the case using the switch statement. BP + if (_entityHandshakeHandlers.hasOwnProperty( type )) { + if (_entityHandshakeHandlers[ type ] is Function) + _entityHandshakeHandlers[ type ]( rec ); + } else { + throw new TLSError( "Unimplemented or unknown handshake type!", TLSError.internal_error ); + } + + // Get set up for the next packet. + if (length+4 0) { + // some implementations don't assign a session ID + rec.readBytes(session, 0, session_length); + } + + _securityParameters.setCipher(rec.readShort()); + _securityParameters.setCompression(rec.readByte()); + _securityParameters.setServerRandom(random); + } + + /** + * Handle HANDSHAKE_CLIENT_HELLO - server side + */ + private function parseHandshakeClientHello( rec:IDataInput ) : void { + var ret:Object; + var ver:uint = rec.readShort(); + if (ver != _securityParameters.version) { + throw new TLSError("Unsupported TLS version: "+ver.toString(16), TLSError.protocol_version); + } + + var random:ByteArray = new ByteArray; + rec.readBytes(random, 0, 32); + var session_length:uint = rec.readByte(); + var session:ByteArray = new ByteArray; + if (session_length > 0) { + // some implementations don't assign a session ID + rec.readBytes(session, 0, session_length); + } + var suites:Array = []; + + var suites_length:uint = rec.readShort(); + for (var i:uint=0;i0) { + var ext_type:uint = rec.readShort(); + var ext_length:uint = rec.readShort(); + var ext_data:ByteArray = new ByteArray; + rec.readBytes(ext_data, 0, ext_length); + ext_total_length -= 4+ext_length; + extensions.push({type:ext_type, length:ext_length, data:ext_data}); + } + } + ret.ext = extensions; + + sendServerHello(ret); + sendCertificate(); + // TODO: Modify to handle case of requesting a certificate from the client, for "client authentication", + // and testing purposes, will probably never actually need it. + sendServerHelloDone(); + } + + private function sendClientHello():void { + var rec:ByteArray = new ByteArray; + // version - modified to support version attribute from ISecurityParameters + rec.writeShort(_securityParameters.version); + // random + var prng:Random = new Random; + var clientRandom:ByteArray = new ByteArray; + prng.nextBytes(clientRandom, 32); + _securityParameters.setClientRandom(clientRandom); + rec.writeBytes(clientRandom,0,32); + // session + rec.writeByte(32); + prng.nextBytes(rec, 32); + // Cipher suites + var cs:Array = _config.cipherSuites; + rec.writeShort(2* cs.length); + for (var i:int=0;i-1) { + return e; + } + } + return -1; + } + + private function sendServerHello(v:Object):void { + var cipher:int = findMatch(_config.cipherSuites, v.suites); + if (cipher == -1) { + throw new TLSError("No compatible cipher found.", TLSError.handshake_failure); + } + _securityParameters.setCipher(cipher); + + var comp:int = findMatch(_config.compressions, v.compressions); + if (comp == 01) { + throw new TLSError("No compatible compression method found.", TLSError.handshake_failure); + } + _securityParameters.setCompression(comp); + _securityParameters.setClientRandom(v.random); + + + var rec:ByteArray = new ByteArray; + rec.writeShort(_securityParameters.version); + var prng:Random = new Random; + var serverRandom:ByteArray = new ByteArray; + prng.nextBytes(serverRandom, 32); + _securityParameters.setServerRandom(serverRandom); + rec.writeBytes(serverRandom,0,32); + // session + rec.writeByte(32); + prng.nextBytes(rec, 32); + // Cipher suite + rec.writeShort(v.suites[0]); + // Compression + rec.writeByte(v.compressions[0]); + rec.position = 0; + sendHandshake(HANDSHAKE_SERVER_HELLO, rec.length, rec); + } + + private var sendClientCert:Boolean = false; + private function setStateRespondWithCertificate( r:ByteArray = null) : void { + sendClientCert = true; + } + + private function sendCertificate( r:ByteArray = null ):void { + var cert:ByteArray = _config.certificate; + var len:uint; + var len2:uint; + var rec:ByteArray = new ByteArray; + // Look for a certficate chain, if we have one, send it, if we don't, send an empty record. + if (cert != null) { + len = cert.length; + len2 = cert.length + 3; + rec.writeByte(len2>>16); + rec.writeShort(len2&65535); + rec.writeByte(len>>16); + rec.writeShort(len&65535); + rec.writeBytes(cert); + } else { + rec.writeShort( 0 ); + rec.writeByte( 0 ); + } + rec.position = 0; + sendHandshake(HANDSHAKE_CERTIFICATE, rec.length, rec); + } + + private function sendCertificateVerify():void { + var rec:ByteArray = new ByteArray(); + // Encrypt the handshake payloads here + var data:ByteArray = _securityParameters.computeCertificateVerify(_entity, _handshakePayloads); + data.position=0; + sendHandshake(HANDSHAKE_CERTIFICATE_VERIFY, data.length, data); + } + + private function sendServerHelloDone():void { + var rec:ByteArray = new ByteArray; + sendHandshake(HANDSHAKE_HELLO_DONE, rec.length, rec); + } + + private function sendClientKeyExchange():void { + if (_securityParameters.useRSA) { + var p:ByteArray = new ByteArray; + p.writeShort(_securityParameters.version); + var prng:Random = new Random; + prng.nextBytes(p, 46); + p.position = 0; + + var preMasterSecret:ByteArray = new ByteArray; + preMasterSecret.writeBytes(p, 0, p.length); + preMasterSecret.position = 0; + _securityParameters.setPreMasterSecret(preMasterSecret); + + var enc_key:ByteArray = new ByteArray; + _otherCertificate.getPublicKey().encrypt(preMasterSecret, enc_key, preMasterSecret.length); + + enc_key.position = 0; + var rec:ByteArray = new ByteArray; + + // TLS requires the size of the premaster key be sent BUT + // SSL 3.0 does not + if (_securityParameters.version > 0x0300) { + rec.writeShort(enc_key.length); + } + rec.writeBytes(enc_key, 0, enc_key.length); + + rec.position=0; + + sendHandshake(HANDSHAKE_CLIENT_KEY_EXCHANGE, rec.length, rec); + + // now is a good time to get our pending states + var o:Object = _securityParameters.getConnectionStates(); + _pendingReadState = o.read; + _pendingWriteState = o.write; + + } else { + throw new TLSError("Non-RSA Client Key Exchange not implemented.", TLSError.internal_error); + } + } + private function sendFinished():void { + var data:ByteArray = _securityParameters.computeVerifyData(_entity, _handshakePayloads); + data.position=0; + sendHandshake(HANDSHAKE_FINISHED, data.length, data); + } + + private function sendHandshake(type:uint, len:uint, payload:IDataInput):void { + var rec:ByteArray = new ByteArray; + rec.writeByte(type); + rec.writeByte(0); + rec.writeShort(len); + payload.readBytes(rec, rec.position, len); + _handshakePayloads.writeBytes(rec, 0, rec.length); + sendRecord(PROTOCOL_HANDSHAKE, rec); + } + + private function sendChangeCipherSpec():void { + var rec:ByteArray = new ByteArray; + rec[0] = 1; + sendRecord(PROTOCOL_CHANGE_CIPHER_SPEC, rec); + + // right after, switch the cipher for writing. + _currentWriteState = _pendingWriteState; + _pendingWriteState = null; + } + + public function sendApplicationData(data:ByteArray, offset:uint=0, length:uint=0):void { + var rec:ByteArray = new ByteArray; + var len:uint = length; + // BIG FAT WARNING: Patch from Arlen Cuss ALA As3crypto group on Google code. + // This addresses data overflow issues when the packet size hits the max length boundary. + if (len == 0) len = data.length; + while (len>16384) { + rec.position = 0; + rec.writeBytes(data, offset, 16384); + rec.position = 0; + sendRecord(PROTOCOL_APPLICATION_DATA, rec); + offset += 16384; + len -= 16384; + } + rec.position = 0; + rec.writeBytes(data, offset, len); + // trace("Data I'm sending..." + Hex.fromArray( data )); + rec.position = 0; + sendRecord(PROTOCOL_APPLICATION_DATA, rec); + } + private function sendRecord(type:uint, payload:ByteArray):void { + // encrypt + payload = _currentWriteState.encrypt(type, payload); + + _oStream.writeByte(type); + _oStream.writeShort(_securityParameters.version); + _oStream.writeShort(payload.length); + _oStream.writeBytes(payload, 0, payload.length); + + scheduleWrite(); + } + + private var _writeScheduler:uint; + private function scheduleWrite():void { + if (_writeScheduler!=0) return; + _writeScheduler = setTimeout(commitWrite, 0); + } + private function commitWrite():void { + clearTimeout(_writeScheduler); + _writeScheduler = 0; + if (_state != STATE_CLOSED) { + dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA)); + } + } + + private function sendClientAck( rec:ByteArray ):void { + if ( _handshakeCanContinue ) { + // If I have a pending cert request, send it + if (sendClientCert) + sendCertificate(); + // send a client key exchange + sendClientKeyExchange(); + // Send the certificate verify, if we have one + if (_config.certificate != null) + sendCertificateVerify(); + // send a change cipher spec + sendChangeCipherSpec(); + // send a finished + sendFinished(); + } + } + + /** + * Vaguely gross function that parses a RSA key out of a certificate. + * + * As long as that certificate looks just the way we expect it to. + * + */ + private function loadCertificates( rec:ByteArray ):void { + var tmp:uint = rec.readByte(); + var certs_len:uint = (tmp<<16) | rec.readShort(); + var certs:Array = []; + + while (certs_len>0) { + tmp = rec.readByte(); + var cert_len:uint = (tmp<<16) | rec.readShort(); + var cert:ByteArray = new ByteArray; + rec.readBytes(cert, 0, cert_len); + certs.push(cert); + certs_len -= 3 + cert_len; + } + + var firstCert:X509Certificate = null; + for (var i:int=0;i