FileDocCategorySizeDatePackage
Record.javaAPI DocphoneME MR2 API (J2ME)39173Wed May 02 18:00:00 BST 2007com.sun.midp.ssl

Record

public class Record extends Object
Implements an SSL record layer that sits atop a TCP connection and beneath the user-visible interface to an SSL socket. It maintains all the state information necessary to encode/decode application data.

Fields Summary
static final byte
CCS
Change Cipher Spec (20).
static final byte
ALRT
Alert (21).
static final byte
HNDSHK
Handshake (22).
static final byte
APP
Application data (23).
static final byte
WARNING
Warning severity level for alerts (1).
static final byte
FATAL
Fatal severity level for alerts (2).
static final byte
CLOSE_NTFY
Close notification alert type (0).
static final byte
UNEXP_MSG
Unexpected message alert type (10).
static final byte
BAD_MAC
Bad MAC alert type (20).
static final byte
HNDSHK_FAIL
Handshake failure alert type (40).
static final byte
NO_CERT
No certificate found alert type (41).
static final byte
BAD_CERT
Bad certificate alert type (42).
static final byte
UNSUP_CERT
Unsupported certificate alert type (43).
static final byte
CERT_REVKD
Certificate revoked alert type (44).
static final byte
CERT_EXPRD
Certificate expired alaert type (45).
static final byte
CERT_UNKWN
Unknown certificate feature alert type (46).
static final byte
BAD_PARAM
Bad parameter alert type (47).
static final byte
SERVER
Server role for SSL record layout (0).
static final byte
CLIENT
Client role for SSL record layout (1).
private final int
HEADER_SIZE
Size of record header
private InputStream
in
Underlying input stream beneath the record layer.
private OutputStream
out
Underlying output stream beneath the record layer.
private byte
rActive
Flag indicating change cipher spec received.
private byte
wActive
Flag indicating change cipher spec has been sent.
private byte
ver
The SSL version in one byte (0x30=3.0).
private byte[]
inputHeader
Current input record header.
private int
headerBytesRead
How many bytes of the record header have been read.
private int
dataLength
How many bytes of the data in the record.
private int
dataBytesRead
How many bytes of the data in the record have been read.
private boolean
shutdown
Shutdown flag, true if connection has been shutdown.
byte[]
inputData
Current input record data.
int
plainTextLength
Length of the plain text in the input buffer
private RecordEncoder
encoder
Records encoder
private RecordDecoder
decoder
Records decoder
Constructors Summary
Record(InputStream ins, OutputStream outs)
Creates a new SSL record layer.

param
ins input stream belonging to the underlying TCP connection
param
outs output stream belonging to the underlying TCP connection

    
    
                                     
        
        in = ins;
        out = outs;
        ver = (byte) 0x30;  // IMPL_NOTE: This is hardcoded for now
    
Methods Summary
public voidalert(byte level, byte type)
Sends an alert message of the specified level and type to the SSL peer.

param
level one of WARNING or FATAL)
param
type one of CLOSE_NTFY, UNEXP_MSG, BAD_MAC, DECOMP_FAIL, HNDSHK_FAIL, NO_CERT, BAD_CERT, UNSUP_CERT, CERT_REVKD, CERT_EXPRD, CERT_UNKWN, BAD_PARAM

        byte[] tmp = new byte[2];
        tmp[0] = level;
        tmp[1] = type;

        try {
            wrRec(ALRT, tmp, 0, 2);
        } catch (IOException e) {
            // ignore, we do not want to step on the real error
        }
    
voidcloseInputStream()
Close input stream

        try {
            in.close();
        } catch (IOException e) {
            // ignore
        }
    
voidcloseOutputStream()
Close output stream

        try {
            out.close();
        } catch (IOException e) {
            // ignore
        }
    
voidinit(byte role, byte[] clientRand, byte[] serverRand, byte suite, byte[] masterSecret)
Chops up a master secret into the client and server MAC secrets, bulk encryption keys and IVs. Also initializes the Cipher and MessageDigest objects used in record encoding/decoding.

param
role role (either CLIENT or SERVER) of this side in the SSL negotiation
param
clientRand 32-byte random value chosen by the client
param
serverRand 32-byte random value chosen by the server
param
suite negotiated cipher suite
param
masterSecret master secret resulting from the key exchange
exception
Exception if the negotiated cipher suite involves an unsupported hash or cipher algorithm

        CipherSuiteData data = new CipherSuiteData(suite);
        data.generateKeys(clientRand, serverRand, masterSecret);

        // depending on role, we choose corresponding MAC secrets 
        // and cipher bulk keys for encoder and decoder
        byte[] encodeSecret;
        byte[] decodeSecret;
        SecretKey decodeCipherKey;
        SecretKey encodeCipherKey;
        if (role == CLIENT) {
            encodeSecret = data.getClientMACSecret();
            decodeSecret = data.getServerMACSecret();
            encodeCipherKey = data.getClientBulkKey();
            decodeCipherKey = data.getServerBulkKey();
        } else {
            encodeSecret = data.getServerMACSecret();
            decodeSecret = data.getClientMACSecret();
            encodeCipherKey = data.getServerBulkKey();
            decodeCipherKey = data.getClientBulkKey();
        }
        
        Cipher encodeCipher = data.getEncodeCipher();
        encodeCipher.init(Cipher.ENCRYPT_MODE, encodeCipherKey);
        Cipher decodeCipher = data.getDecodeCipher();
        decodeCipher.init(Cipher.DECRYPT_MODE, decodeCipherKey);
        
        encoder = new RecordEncoder(data.getEncodeDigest(), encodeSecret,
                data.getPadLength(), encodeCipher);
        decoder = new RecordDecoder(data.getDecodeDigest(), decodeSecret,
                data.getPadLength(), decodeCipher);
    
voidrdRec(boolean block, byte type)
Reads and returns a record (including the 5-byte header) of the specified type. If the caller asks for application data and a close_notify warning alert is found as the next available record, this method sets plainTextLength to -1 to signal the end of the input stream.

param
block if true the method will not return until data is available, or end of stream
param
type desired SSL record type
exception
IOException if an unexpected record type or SSL alert is found in the underlying sockets input stream

        if (!rdRec(block)) {
            return;
        }

        if (inputHeader[0] == type) {
            // success
            return;
        }

        // Signal end of stream.
        plainTextLength = -1;

        switch (inputHeader[0]) {
        case CCS: 
            // Can change_cipher_spec can be passed to handshake clients?
            // if (type == HNDSHK) return r; // fall through otherwise
        case HNDSHK:
        case APP:
        default:
            alert(FATAL, UNEXP_MSG);
            throw new IOException("Unexpected SSL record, type: " +
                                  inputHeader[0]);
            
        case ALRT:
            // An Alert record needs to be atleast 2 bytes of data
            if (inputData.length < 2) {
                throw new IOException("Bad alert length");
            }

            if ((inputData[0] == WARNING) && 
                (inputData[1] == CLOSE_NTFY) && 
                (type == APP)) {
                /*
                 * We got a close_notify warning
                 * Shutdown the connection.
                 */ 
                shutdownConnection();
                return;  // signal end of InputStream
            }

            if ((inputData[0] < WARNING) || (inputData[0] > FATAL)) {
                throw new IOException("Bad alert level");
            }

            throw new IOException("Alert (" + inputData[0] + 
                                  "," + inputData[1] + ")");
        }
    
private booleanrdRec(boolean block)
Returns the next record read from the record layer (the 5-byte SSL record header is included). Set plainTextLength to length of the record or -1 for end of stream.

param
block if true the method will not return until data is available, or end of stream
return
true if a record has been read
exception
IOException if an I/O error occurs

        int b;

        plainTextLength = 0;

        if (!block && in.available() == 0) {
            return false;
        }

        /*
         * This method could have returned last time after reading the
         * part of the record, in that case headerBytesRead will not be 0.
         */
        if (headerBytesRead == 0) {
            b = in.read(inputHeader, 0, 1);
            if (b == -1) {
                /*
                 * Peer closed SSL connection without close_notify
                 */ 
                plainTextLength = -1;
                return false;
            }

            headerBytesRead = 1;
            dataBytesRead = 0;
            dataLength = 0;
        }

        while (headerBytesRead < inputHeader.length) {
            if (!block && in.available() == 0) {
                return false;
            }

            b = in.read(inputHeader, headerBytesRead,
                        inputHeader.length - headerBytesRead);
            if (b == -1) {
                throw new IOException("SSL connection ended abnormally " +
                                      "while reading record header");
            }

            headerBytesRead += b;
        }

        /*
         * This method could have returned last time after reading the
         * header but not all of the data, in that case dataLength
         * will not be 0.
         */
        if (dataLength == 0) {
            // Check record type and version
            if ((inputHeader[0] < CCS) || 
                (inputHeader[0] > APP) ||
                (inputHeader[1] != (byte) (ver >>> 4)) ||
                (inputHeader[2] != (byte) (ver & 0x0f))) {
                alert(FATAL, UNEXP_MSG);
                throw new IOException("Bad record type (" + inputHeader[0] + 
                                  ") or version (" + inputHeader[1] + "." +
                                  inputHeader[2] + ")");
            }
        
            dataLength = ((inputHeader[3] & 0xff) << 8) + 
                (inputHeader[4] & 0xff);
            inputData = new byte[dataLength];
        }

        while (dataBytesRead < dataLength) {
            if (!block && in.available() == 0) {
                return false;
            }

            b = in.read(inputData, dataBytesRead, dataLength - dataBytesRead);
            if (b == -1) {
                throw new IOException("SSL connection ended abnormally " +
                                      "after reading record byte " +
                                      (dataBytesRead + headerBytesRead));
            }

            dataBytesRead += b;
        }

        if (rActive == 1) {
            try {
                plainTextLength = decoder.decode(inputHeader, inputData);
            } catch (IOException e) {
                if (e.getMessage().compareTo("Bad MAC") == 0) {
                    alert(FATAL, BAD_MAC);
                } else {
                    throw e;
                }
            }
        } else {
            plainTextLength = dataBytesRead;
        }

        if (inputHeader[0] == CCS) {
            rActive = 1;
        }

        // start with a new record header next time
        headerBytesRead = 0;

        return true;
    
public voidshutdownConnection()
Send a close notify and shutdown the TCP connection if needed.

        if (shutdown) {
            return;
        }

        alert(Record.WARNING, Record.CLOSE_NTFY);
        shutdown = true;
        closeOutputStream();
        closeInputStream();
    
voidwrRec(byte type, byte[] buf, int off, int len)
Writes an SSL record to the underlying socket's output stream.

param
type record type (one of CCS, ALRT, HNDSHK or APP)
param
buf byte array containing the record body (i.e. everything but the 5-byte header)
param
off starting offset of the record body inside buf
param
len length of the record body, the maximum is 2^14 +2048 as defined by RFC 2246
exception
IOException if an I/O error occurs.

        byte[] rec;

        if (shutdown) {
            throw new IOException("Server has shutdown the connection");
        }

        /*
         * Create a new byte array with room for the header and
         * fill the record header with type, version and length
         */
        rec = new byte[len + 5];
        rec[0] = type;
        rec[1] = (byte) (ver >>> 4);
        rec[2] = (byte) (ver & 0x0f);
        rec[3] = (byte) (len >>> 8);
        rec[4] = (byte) (len & 0xff);
        // Fill the rest of the record
        System.arraycopy(buf, off, rec, 5, len);
        if (wActive == 1) {
            out.write(encoder.encode(rec));
        } else {
            out.write(rec);
        }
        if (type == CCS) wActive = 1;