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