--- a/jdk/src/share/classes/sun/security/ssl/OutputRecord.java Thu Feb 28 16:36:01 2013 -0800
+++ b/jdk/src/share/classes/sun/security/ssl/OutputRecord.java Fri Mar 01 02:34:34 2013 -0800
@@ -54,6 +54,7 @@
private int lastHashed;
private boolean firstMessage;
final private byte contentType;
+ private int headerOffset;
// current protocol version, sent as record version
ProtocolVersion protocolVersion;
@@ -70,6 +71,23 @@
* Default constructor makes a record supporting the maximum
* SSL record size. It allocates the header bytes directly.
*
+ * The structure of the byte buffer looks like:
+ *
+ * |---------+--------+-------+---------------------------------|
+ * | unused | header | IV | content, MAC/TAG, padding, etc. |
+ * | headerPlusMaxIVSize |
+ *
+ * unused: unused part of the buffer of size
+ *
+ * headerPlusMaxIVSize - header size - IV size
+ *
+ * When this object is created, we don't know the protocol
+ * version number, IV length, etc., so reserve space in front
+ * to avoid extra data movement (copies).
+ * header: the header of an SSL record
+ * IV: the optional IV/nonce field, it is only required for block
+ * (TLS 1.1 or later) and AEAD cipher suites.
+ *
* @param type the content type for the record
*/
OutputRecord(byte type, int size) {
@@ -77,9 +95,10 @@
this.protocolVersion = ProtocolVersion.DEFAULT;
this.helloVersion = ProtocolVersion.DEFAULT_HELLO;
firstMessage = true;
- count = headerSize;
+ count = headerPlusMaxIVSize;
contentType = type;
lastHashed = count;
+ headerOffset = headerPlusMaxIVSize - headerSize;
}
OutputRecord(byte type) {
@@ -119,8 +138,9 @@
@Override
public synchronized void reset() {
super.reset();
- count = headerSize;
+ count = headerPlusMaxIVSize;
lastHashed = count;
+ headerOffset = headerPlusMaxIVSize - headerSize;
}
/*
@@ -173,58 +193,84 @@
* of sending empty records over the network.
*/
boolean isEmpty() {
- return count == headerSize;
+ return count == headerPlusMaxIVSize;
}
/*
- * Return true if the record is of a given alert.
+ * Return true if the record is of an alert of the given description.
+ *
+ * Per SSL/TLS specifications, alert messages convey the severity of the
+ * message (warning or fatal) and a description of the alert. An alert
+ * is defined with a two bytes struct, {byte level, byte description},
+ * following after the header bytes.
*/
boolean isAlert(byte description) {
- // An alert is defined with a two bytes struct,
- // {byte level, byte description}, following after the header bytes.
- if (count > (headerSize + 1) && contentType == ct_alert) {
- return buf[headerSize + 1] == description;
+ if ((count > (headerPlusMaxIVSize + 1)) && (contentType == ct_alert)) {
+ return buf[headerPlusMaxIVSize + 1] == description;
}
return false;
}
/*
- * Compute the MAC and append it to this record. In case we
- * are automatically flushing a handshake stream, make sure we
- * have hashed the message first.
+ * Encrypt ... length may grow due to block cipher padding, or
+ * message authentication code or tag.
*/
- void addMAC(MAC signer) throws IOException {
+ void encrypt(Authenticator authenticator, CipherBox box)
+ throws IOException {
+
+ // In case we are automatically flushing a handshake stream, make
+ // sure we have hashed the message first.
//
// when we support compression, hashing can't go here
// since it'll need to be done on the uncompressed data,
// and the MAC applies to the compressed data.
- //
if (contentType == ct_handshake) {
doHashes();
}
- if (signer.MAClen() != 0) {
- byte[] hash = signer.compute(contentType, buf,
- headerSize, count - headerSize);
- write(hash);
+
+ // Requires message authentication code for stream and block
+ // cipher suites.
+ if (authenticator instanceof MAC) {
+ MAC signer = (MAC)authenticator;
+ if (signer.MAClen() != 0) {
+ byte[] hash = signer.compute(contentType, buf,
+ headerPlusMaxIVSize, count - headerPlusMaxIVSize);
+ write(hash);
+ }
+ }
+
+ if (!box.isNullCipher()) {
+ // Requires explicit IV/nonce for CBC/AEAD cipher suites for
+ // TLS 1.1 or later.
+ if ((protocolVersion.v >= ProtocolVersion.TLS11.v) &&
+ (box.isCBCMode() || box.isAEADMode())) {
+ byte[] nonce = box.createExplicitNonce(authenticator,
+ contentType, count - headerPlusMaxIVSize);
+ int offset = headerPlusMaxIVSize - nonce.length;
+ System.arraycopy(nonce, 0, buf, offset, nonce.length);
+ headerOffset = offset - headerSize;
+ } else {
+ headerOffset = headerPlusMaxIVSize - headerSize;
+ }
+
+ // encrypt the content
+ int offset = headerPlusMaxIVSize;
+ if (!box.isAEADMode()) {
+ // The explicit IV can be encrypted.
+ offset = headerOffset + headerSize;
+ } // Otherwise, DON'T encrypt the nonce_explicit for AEAD mode
+
+ count = offset + box.encrypt(buf, offset, count - offset);
}
}
/*
- * Encrypt ... length may grow due to block cipher padding
- */
- void encrypt(CipherBox box) {
- int len = count - headerSize;
- count = headerSize + box.encrypt(buf, headerSize, len);
- }
-
-
- /*
* Tell how full the buffer is ... for filling it with application or
* handshake data.
*/
final int availableDataBytes() {
- int dataSize = count - headerSize;
+ int dataSize = count - headerPlusMaxIVSize;
return maxDataSize - dataSize;
}
@@ -270,11 +316,11 @@
* Don't emit content-free records. (Even change cipher spec
* messages have a byte of data!)
*/
- if (count == headerSize) {
+ if (count == headerPlusMaxIVSize) {
return;
}
- int length = count - headerSize;
+ int length = count - headerOffset - headerSize;
// "should" really never write more than about 14 Kb...
if (length < 0) {
throw new SSLException("output record size too small: "
@@ -299,7 +345,9 @@
*/
if (firstMessage && useV2Hello()) {
byte[] v3Msg = new byte[length - 4];
- System.arraycopy(buf, headerSize + 4, v3Msg, 0, v3Msg.length);
+ System.arraycopy(buf, headerPlusMaxIVSize + 4,
+ v3Msg, 0, v3Msg.length);
+ headerOffset = 0; // reset the header offset
V3toV2ClientHello(v3Msg);
handshakeHash.reset();
lastHashed = 2;
@@ -314,11 +362,11 @@
/*
* Fill out the header, write it and the message.
*/
- buf[0] = contentType;
- buf[1] = protocolVersion.major;
- buf[2] = protocolVersion.minor;
- buf[3] = (byte)(length >> 8);
- buf[4] = (byte)(length);
+ buf[headerOffset + 0] = contentType;
+ buf[headerOffset + 1] = protocolVersion.major;
+ buf[headerOffset + 2] = protocolVersion.minor;
+ buf[headerOffset + 3] = (byte)(length >> 8);
+ buf[headerOffset + 4] = (byte)(length);
}
firstMessage = false;
@@ -338,7 +386,8 @@
* when holdRecord is true, the implementation in this class
* will be used.
*/
- writeBuffer(heldRecordBuffer, buf, 0, count, debugOffset);
+ writeBuffer(heldRecordBuffer,
+ buf, headerOffset, count - headerOffset, debugOffset);
} else {
// It's time to send, do we have buffered data?
// May or may not have a heldRecordBuffer.
@@ -346,15 +395,18 @@
int heldLen = heldRecordBuffer.size();
// Ensure the capacity of this buffer.
- ensureCapacity(count + heldLen);
+ int newCount = count + heldLen - headerOffset;
+ ensureCapacity(newCount);
// Slide everything in the buffer to the right.
- System.arraycopy(buf, 0, buf, heldLen, count);
+ System.arraycopy(buf, headerOffset,
+ buf, heldLen, count - headerOffset);
// Prepend the held record to the buffer.
System.arraycopy(
heldRecordBuffer.toByteArray(), 0, buf, 0, heldLen);
- count += heldLen;
+ count = newCount;
+ headerOffset = 0;
// Clear the held buffer.
heldRecordBuffer.reset();
@@ -362,7 +414,8 @@
// The held buffer has been dumped, set the debug dump offset.
debugOffset = heldLen;
}
- writeBuffer(s, buf, 0, count, debugOffset);
+ writeBuffer(s, buf, headerOffset,
+ count - headerOffset, debugOffset);
}
reset();
@@ -382,12 +435,11 @@
if (debug != null && Debug.isOn("packet")) {
try {
HexDumpEncoder hd = new HexDumpEncoder();
- ByteBuffer bb = ByteBuffer.wrap(
- buf, off + debugOffset, len - debugOffset);
System.out.println("[Raw write]: length = " +
- bb.remaining());
- hd.encodeBuffer(bb, System.out);
+ (len - debugOffset));
+ hd.encodeBuffer(new ByteArrayInputStream(buf,
+ off + debugOffset, len - debugOffset), System.out);
} catch (IOException e) { }
}
}
@@ -400,8 +452,13 @@
return firstMessage
&& (helloVersion == ProtocolVersion.SSL20Hello)
&& (contentType == ct_handshake)
- && (buf[5] == HandshakeMessage.ht_client_hello)
- && (buf[headerSize + 4+2+32] == 0); // V3 session ID is empty
+ && (buf[headerOffset + 5] == HandshakeMessage.ht_client_hello)
+ // 5: recode header size
+ && (buf[headerPlusMaxIVSize + 4 + 2 + 32] == 0);
+ // V3 session ID is empty
+ // 4: handshake header size
+ // 2: client_version in ClientHello
+ // 32: random in ClientHello
}
/*