SSLRecordProtocolpublic 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_LENGTHMaximum length of allowed plain data fragment
as specified by TLS specification. | protected static int | MAX_COMPRESSED_DATA_LENGTHMaximum length of allowed compressed data fragment
as specified by TLS specification. | protected static int | MAX_CIPHERED_DATA_LENGTHMaximum length of allowed ciphered data fragment
as specified by TLS specification. | protected static int | MAX_SSL_PACKET_SIZEMaximum 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 |
Methods Summary |
---|
protected void | alert(byte level, byte description)Passes the alert information to the alert protocol.
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.
// 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 int | getDataSize(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 int | getMinRecordSize()Returns the minimum possible length of the SSL record.
return (activeReadState == null)
? 6 // type + version + length + 1 byte of data
: 5 + activeReadState.getMinFragmentSize();
| protected int | getRecordSize(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.SSLSessionImpl | getSession()Returns the session obtained during the handshake negotiation.
If the handshake process was not compleated, method returns null.
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 void | setSession(org.apache.harmony.xnet.provider.jsse.SSLSessionImpl session)Set the ssl session to be used after sending the changeCipherSpec message
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 void | setVersion(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.
this.version = ver;
| protected void | shutdown()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 int | unwrap()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.
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.
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.
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);
|
|