⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tlsengine.as

📁 As3 Crypto is a cryptography library written in Actionscript 3 that provides several common algorith
💻 AS
📖 第 1 页 / 共 2 页
字号:
/**
 * TLSEngine
 * 
 * A TLS protocol implementation.
 * See comment below for some details.
 * Copyright (c) 2007 Henri Torgemane
 * 
 * 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 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;

	[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 static const TLS_VERSION:uint = 0x0301;
		
		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:TLSSecurityParameters;
		
		private var _currentReadState:TLSConnectionState;
		private var _currentWriteState:TLSConnectionState;
		private var _pendingReadState:TLSConnectionState;
		private var _pendingWriteState:TLSConnectionState;
		
		private var _handshakePayloads:ByteArray;
		
		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;
		// If this isn't null, we expect this identity to be found in the Cert's Subject CN.
		private var _otherIdentity: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;
			
			_securityParameters = new TLSSecurityParameters(_entity);
			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);
				}
				if (ver != TLS_VERSION) {
					throw new TLSError("Unsupported TLS version: "+ver.toString(16), TLSError.protocol_version);
				}
				if (stream.bytesAvailable<length) {
					
				}
				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});
				}
			}
		}
		
		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);
			}
			switch (type) {
				case PROTOCOL_APPLICATION_DATA:
					if (_state == STATE_READY) {
						parseApplicationData(p);
					} else {
						throw new TLSError("Too soon for data!", TLSError.unexpected_message);
					}
					break;
				case PROTOCOL_HANDSHAKE:
					while (p!=null) {
						p = parseHandshake(p);
					}
					break;
				case PROTOCOL_ALERT:
					parseAlert(p);
					break;
				case PROTOCOL_CHANGE_CIPHER_SPEC:
					parseChangeCipherSpec(p);
					break;
				default:
					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;

		/**
		 * The handshake is always started by the client.
		 * 
		 */
		private function startHandshake():void {
			_state = STATE_NEGOTIATING;
			// reset some other handshake state. XXX
			sendClientHello();
		}
		
		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 (p[0]!=HANDSHAKE_FINISHED) {
				_handshakePayloads.writeBytes(p, 0, length+4);
			}
			
			switch (type) {
				case HANDSHAKE_HELLO_REQUEST:
					if (!enforceClient()) break;
					if (_state != STATE_READY) {
						trace("Received an HELLO_REQUEST before being in state READY. ignoring.");
						break;
					}
					_handshakePayloads = new ByteArray;
					startHandshake();
					break;
				case HANDSHAKE_CLIENT_HELLO:
					if (!enforceServer()) break;
					var v:Object = parseHandshakeHello(type, length, rec);
					sendServerHello(v);
					sendCertificate();
					sendServerHelloDone();
					break;
				case HANDSHAKE_SERVER_HELLO:
					if (!enforceClient()) break;
					v = parseHandshakeHello(type, length, rec);
					_securityParameters.setCipher(v.suites[0]);
					_securityParameters.setCompression(v.compressions[0]);
					_securityParameters.setServerRandom(v.random);
					break;
				case HANDSHAKE_CERTIFICATE:
					// okay to receive on both sides.
					tmp = 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;
					}
					loadCertificates(certs);
					break;
				case HANDSHAKE_SERVER_KEY_EXCHANGE:
					if (!enforceClient()) break;
					throw new TLSError("Server Key Exchange Not Implemented", TLSError.internal_error);
					break;
				case HANDSHAKE_CERTIFICATE_REQUEST:
					if (!enforceClient()) break;
					throw new TLSError("Certificate Request Not Implemented", TLSError.internal_error);
					break;
				case HANDSHAKE_HELLO_DONE:
					if (!enforceClient()) break;
					sendClientAck();
					break;
				case HANDSHAKE_CLIENT_KEY_EXCHANGE:
					if (!enforceServer()) break;
					parseHandshakeClientKeyExchange(type, length, rec);
					break;
				case HANDSHAKE_CERTIFICATE_VERIFY:
					if (!enforceServer()) break;
					throw new TLSError("Certificate Verify not implemented", TLSError.internal_error);
					break;
				case HANDSHAKE_FINISHED:
					// okay to receive on both sides
					var verifyData:ByteArray = new ByteArray;
					rec.readBytes(verifyData, 0, 12);
					verifyHandshake(verifyData);
					break;
			}

			if (length+4<p.length) {
				var n:ByteArray = new ByteArray;
				n.writeBytes(p,length+4, p.length-(length+4));
				return n;
			} else {

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -