FileDocCategorySizeDatePackage
SSLRecordProtocol.javaAPI DocAndroid 1.5 API19493Wed May 06 22:41:06 BST 2009org.apache.harmony.xnet.provider.jsse

SSLRecordProtocol

public class SSLRecordProtocol extends Object
This class performs functionality dedicated to SSL record layer. It unpacks and routes income data to the appropriate client protocol (handshake, alert, application data protocols) and paketizes outcome data into SSL/TLS records. Initially created object has null connection state and does not perform any cryptography computations over the income/outcome data. After handshake protocol agreed upon security parameters they are placed into SSLSessionImpl object and available for record protocol as pending session. The order of setting up of the pending session as an active session differs for client and server modes. So for client mode the parameters are provided by handshake protocol during retrieving of change_cipher_spec message to be sent (by calling of getChangeCipherSpecMesage method). For server side mode record protocol retrieves the parameters from handshake protocol after receiving of client's change_cipher_spec message. After the pending session has been setted up as a curent session, new connectin state object is created and used for encryption/decryption of the messages. Among with base functionality this class provides the information about constrains on the data length, and information about correspondance of plain and encrypted data lengths. For more information on TLS v1 see http://www.ietf.org/rfc/rfc2246.txt, on SSL v3 see http://wp.netscape.com/eng/ssl3, on SSL v2 see http://wp.netscape.com/eng/security/SSL_2.html.

Fields Summary
protected static int
MAX_DATA_LENGTH
Maximum length of allowed plain data fragment as specified by TLS specification.
protected static int
MAX_COMPRESSED_DATA_LENGTH
Maximum length of allowed compressed data fragment as specified by TLS specification.
protected static int
MAX_CIPHERED_DATA_LENGTH
Maximum length of allowed ciphered data fragment as specified by TLS specification.
protected static int
MAX_SSL_PACKET_SIZE
Maximum length of ssl record. It is counted as: type(1) + version(2) + length(2) + MAX_CIPHERED_DATA_LENGTH
private org.apache.harmony.xnet.provider.jsse.SSLSessionImpl
session
private byte[]
version
private org.apache.harmony.xnet.provider.jsse.SSLInputStream
in
private HandshakeProtocol
handshakeProtocol
private AlertProtocol
alertProtocol
private org.apache.harmony.xnet.provider.jsse.Appendable
appData
private ConnectionState
activeReadState
private ConnectionState
activeWriteState
private ConnectionState
pendingConnectionState
private Logger.Stream
logger
private boolean
sessionWasChanged
private static final byte[]
change_cipher_spec_byte
Constructors Summary
protected SSLRecordProtocol(HandshakeProtocol handshakeProtocol, AlertProtocol alertProtocol, org.apache.harmony.xnet.provider.jsse.SSLInputStream in, org.apache.harmony.xnet.provider.jsse.Appendable appData)
Creates an instance of record protocol and tunes up the client protocols to use ut.

param
handshakeProtocol: HandshakeProtocol
param
alertProtocol: AlertProtocol
param
in: SSLInputStream
param
appData: Appendable


                                                 
      
             
             
              
        this.handshakeProtocol = handshakeProtocol;
        this.handshakeProtocol.setRecordProtocol(this);
        this.alertProtocol = alertProtocol;
        this.alertProtocol.setRecordProtocol(this);
        this.in = in;
        this.appData = appData;
    
Methods Summary
protected voidalert(byte level, byte description)
Passes the alert information to the alert protocol.

param
level: byte
param
description: byte

        if (logger != null) {
            logger.println("SSLRecordProtocol.allert: "+level+" "+description);
        }
        alertProtocol.alert(level, description);
    
protected byte[]getChangeCipherSpecMesage(org.apache.harmony.xnet.provider.jsse.SSLSessionImpl session)
Returns the change cipher spec message to be sent to another peer. The pending connection state will be built on the base of provided session object The calling of this method triggers pending write connection state to be active.

return
ssl record containing the "change cipher spec" message.

        // make change_cipher_spec_message:
        byte[] change_cipher_spec_message;
        if (activeWriteState == null) {
            change_cipher_spec_message = new byte[] {
                    ContentType.CHANGE_CIPHER_SPEC, version[0],
                        version[1], 0, 1, 1
                };
        } else {
            change_cipher_spec_message =
                packetize(ContentType.CHANGE_CIPHER_SPEC, version,
                        activeWriteState.encrypt(ContentType.CHANGE_CIPHER_SPEC,
                            change_cipher_spec_byte, 0, 1));
        }
        setSession(session);
        activeWriteState = pendingConnectionState;
        if (logger != null) {
            logger.println("SSLRecordProtocol.getChangeCipherSpecMesage");
            logger.println("activeWriteState = pendingConnectionState");
            logger.print(change_cipher_spec_message);
        }
        return change_cipher_spec_message;
    
protected intgetDataSize(int record_size)
Returns the upper bound of length of data containing in the record with specified length. If the provided record_size is greater or equal to MAX_CIPHERED_DATA_LENGTH the returned value will be MAX_DATA_LENGTH counted as for data with MAX_CIPHERED_DATA_LENGTH length.

        record_size -= 5; // - (type + version + length + data_size)
        if (record_size > MAX_CIPHERED_DATA_LENGTH) {
            // the data of such size consists of the several packets
            return MAX_DATA_LENGTH;
        }
        if (activeReadState == null) {
            return record_size;
        } else {
            return activeReadState.getContentSize(record_size);
        }
    
protected intgetMinRecordSize()
Returns the minimum possible length of the SSL record.

return

        return (activeReadState == null)
            ? 6 // type + version + length + 1 byte of data
            : 5 + activeReadState.getMinFragmentSize();
    
protected intgetRecordSize(int data_size)
Returns the record length for the specified incoming data length. If actual resulting record length is greater than MAX_CIPHERED_DATA_LENGTH, MAX_CIPHERED_DATA_LENGTH is returned.

        if (activeWriteState == null) {
            return 5+data_size; // type + version + length + data_size
        } else {
            int res = 5 + activeWriteState.getFragmentSize(data_size);
            return (res > MAX_CIPHERED_DATA_LENGTH)
                ? MAX_CIPHERED_DATA_LENGTH // so the source data should be
                                           // splitted into several packets
                : res;
        }
    
protected org.apache.harmony.xnet.provider.jsse.SSLSessionImplgetSession()
Returns the session obtained during the handshake negotiation. If the handshake process was not compleated, method returns null.

return
the session in effect.

        return session;
    
private byte[]packetize(byte type, byte[] version, byte[] fragment)

        byte[] buff = new byte[5+fragment.length];
        buff[0] = type;
        if (version != null) {
            buff[1] = version[0];
            buff[2] = version[1];
        } else {
            buff[1] = 3;
            buff[2] = 1;
        }
        buff[3] = (byte) ((0x00FF00 & fragment.length) >> 8);
        buff[4] = (byte) (0x0000FF & fragment.length);
        System.arraycopy(fragment, 0, buff, 5, fragment.length);
        return buff;
    
private voidsetSession(org.apache.harmony.xnet.provider.jsse.SSLSessionImpl session)
Set the ssl session to be used after sending the changeCipherSpec message

param
session: SSLSessionImpl

        if (!sessionWasChanged) {
            // session was not changed for current handshake process
            if (logger != null) {
                logger.println("SSLRecordProtocol.setSession: Set pending session");
                logger.println("  cipher name: " + session.getCipherSuite());
            }
            this.session = session;
            // create new connection state
            pendingConnectionState = ((version == null) || (version[1] == 1))
                ? (ConnectionState) new ConnectionStateTLS(getSession())
                : (ConnectionState) new ConnectionStateSSLv3(getSession());
            sessionWasChanged = true;
        } else {
            // wait for rehandshaking's session
            sessionWasChanged = false;
        }
    
protected voidsetVersion(byte[] ver)
Sets up the SSL version used in this connection. This method is calling from the hanshake protocol after it becomes known witch protocol version will be used.

param
ver: byte[]
return

        this.version = ver;
    
protected voidshutdown()
Shutdownes the protocol. It will be impossiblke to use the instance after the calling of this method.

        session = null;
        version = null;
        in = null;
        handshakeProtocol = null;
        alertProtocol = null;
        appData = null;
        if (pendingConnectionState != null) {
            pendingConnectionState.shutdown();
        }
        pendingConnectionState = null;
        if (activeReadState != null) {
            activeReadState.shutdown();
        }
        activeReadState = null;
        if (activeReadState != null) {
            activeReadState.shutdown();
        }
        activeWriteState = null;
    
protected intunwrap()
Retrieves the fragment field of TLSCiphertext, and than depending on the established Connection State decrypts and decompresses it. The following structure is expected on the input at the moment of the call: struct { ContentType type; ProtocolVersion version; uint16 length; select (CipherSpec.cipher_type) { case stream: GenericStreamCipher; case block: GenericBlockCipher; } fragment; } TLSCiphertext; (as specified by RFC 2246, TLS v1 Protocol specification) In addition this method can recognize SSLv2 hello message which are often used to establish the SSL/TLS session.

throws
IOException if some io errors have been occured
throws
EndOfSourceException if underlying input stream has ran out of data.
throws
EndOfBufferException if there was not enought data to build complete ssl packet.
return
the type of unwrapped message.

        if (logger != null) {
            logger.println("SSLRecordProtocol.unwrap: BEGIN [");
        }
        int type = in.readUint8();
        if ((type < ContentType.CHANGE_CIPHER_SPEC)
                || (type > ContentType.APPLICATION_DATA)) {
            if (logger != null) {
                logger.println("Non v3.1 message type:" + type);
            }
            if (type >= 0x80) {
                // it is probably SSL v2 client_hello message
                // (see SSL v2 spec at:
                // http://wp.netscape.com/eng/security/SSL_2.html)
                int length = (type & 0x7f) << 8 | in.read();
                byte[] fragment = in.read(length);
                handshakeProtocol.unwrapSSLv2(fragment);
                if (logger != null) {
                    logger.println(
                            "SSLRecordProtocol:unwrap ] END, SSLv2 type");
                }
                return ContentType.HANDSHAKE;
            }
            throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
                    new SSLProtocolException(
                        "Unexpected message type has been received: "+type));
        }
        if (logger != null) {
            logger.println("Got the message of type: " + type);
        }
        if (version != null) {
            if ((in.read() != version[0])
                    || (in.read() != version[1])) {
                throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
                        new SSLProtocolException(
                            "Unexpected message type has been received: " +
                            type));
            }
        } else {
            in.skip((long) 2); // just skip the version number
        }
        int length = in.readUint16();
        if (logger != null) {
            logger.println("TLSCiphertext.fragment["+length+"]: ...");
        }
        if (length > MAX_CIPHERED_DATA_LENGTH) {
            throw new AlertException(AlertProtocol.RECORD_OVERFLOW,
                    new SSLProtocolException(
                        "Received message is too big."));
        }
        byte[] fragment = in.read(length);
        if (logger != null) {
            logger.print(fragment);
        }
        if (activeReadState != null) {
            fragment = activeReadState.decrypt((byte) type, fragment);
            if (logger != null) {
                logger.println("TLSPlaintext.fragment:");
                logger.print(fragment);
            }
        }
        if (fragment.length > MAX_DATA_LENGTH) {
            throw new AlertException(AlertProtocol.DECOMPRESSION_FAILURE,
                    new SSLProtocolException(
                        "Decompressed plain data is too big."));
        }
        switch (type) {
            case ContentType.CHANGE_CIPHER_SPEC:
                // notify handshake protocol:
                handshakeProtocol.receiveChangeCipherSpec();
                setSession(handshakeProtocol.getSession());
                // change cipher spec message has been received, so:
                if (logger != null) {
                    logger.println("activeReadState = pendingConnectionState");
                }
                activeReadState = pendingConnectionState;
                break;
            case ContentType.ALERT:
                alert(fragment[0], fragment[1]);
                break;
            case ContentType.HANDSHAKE:
                handshakeProtocol.unwrap(fragment);
                break;
            case ContentType.APPLICATION_DATA:
                if (logger != null) {
                    logger.println(
                            "TLSCiphertext.unwrap: APP DATA["+length+"]:");
                    logger.println(new String(fragment));
                }
                appData.append(fragment);
                break;
            default:
                throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
                        new SSLProtocolException(
                            "Unexpected message type has been received: " +
                            type));
        }
        if (logger != null) {
            logger.println("SSLRecordProtocol:unwrap ] END, type: " + type);
        }
        return type;
    
protected byte[]wrap(byte content_type, DataStream dataStream)
Depending on the Connection State (Session) encrypts and compress the provided data, and packs it into TLSCiphertext structute.

param
content_type: int
param
fragment: byte[]
return
ssl packet created over the current connection state

        byte[] fragment = dataStream.getData(MAX_DATA_LENGTH);
        return wrap(content_type, fragment, 0, fragment.length);
    
protected byte[]wrap(byte content_type, byte[] fragment, int offset, int len)
Depending on the Connection State (Session) encrypts and compress the provided data, and packs it into TLSCiphertext structute.

param
content_type: int
param
fragment: byte[]
return
ssl packet created over the current connection state

        if (logger != null) {
            logger.println("SSLRecordProtocol.wrap: TLSPlaintext.fragment["
                    +len+"]:");
            logger.print(fragment, offset, len);
        }
        if (len > MAX_DATA_LENGTH) {
            throw new AlertException(
                AlertProtocol.INTERNAL_ERROR,
                new SSLProtocolException(
                    "The provided chunk of data is too big: " + len
                    + " > MAX_DATA_LENGTH == "+MAX_DATA_LENGTH));
        }
        byte[] ciphered_fragment = fragment;
        if (activeWriteState != null) {
            ciphered_fragment =
                activeWriteState.encrypt(content_type, fragment, offset, len);
            if (ciphered_fragment.length > MAX_CIPHERED_DATA_LENGTH) {
                throw new AlertException(
                    AlertProtocol.INTERNAL_ERROR,
                    new SSLProtocolException(
                        "The ciphered data increased more than on 1024 bytes"));
            }
            if (logger != null) {
                logger.println("SSLRecordProtocol.wrap: TLSCiphertext.fragment["
                        +ciphered_fragment.length+"]:");
                logger.print(ciphered_fragment);
            }
        }
        return packetize(content_type, version, ciphered_fragment);