📄 tlsengine.as
字号:
/**
* 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 + -