jdk/src/share/classes/sun/security/ssl/OutputRecord.java
changeset 16045 9d08c3b9a6a0
parent 14664 e71aa0962e70
child 16067 36055e4b5305
--- 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
     }
 
     /*