src/java.base/share/classes/sun/security/ssl/InputRecord.java
branchJDK-8145252-TLS13-branch
changeset 56542 56aaa6cb3693
parent 47216 71c04702a3d5
child 56694 aa54a1f8e426
--- a/src/java.base/share/classes/sun/security/ssl/InputRecord.java	Fri May 11 14:55:56 2018 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/InputRecord.java	Fri May 11 15:53:12 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,14 +27,8 @@
 
 import java.io.*;
 import java.nio.*;
-import java.util.*;
-
 import javax.crypto.BadPaddingException;
-
-import javax.net.ssl.*;
-
-import sun.security.util.HexDumpEncoder;
-
+import sun.security.ssl.SSLCipher.SSLReadCipher;
 
 /**
  * {@code InputRecord} takes care of the management of SSL/TLS/DTLS input
@@ -42,15 +36,10 @@
  *
  * @author David Brownell
  */
-class InputRecord implements Record, Closeable {
-
-    /* Class and subclass dynamic debugging support */
-    static final Debug debug = Debug.getInstance("ssl");
+abstract class InputRecord implements Record, Closeable {
+    SSLReadCipher       readCipher;
 
-    Authenticator       readAuthenticator;
-    CipherBox           readCipher;
-
-    HandshakeHash       handshakeHash;
+    final HandshakeHash handshakeHash;
     boolean             isClosed;
 
     // The ClientHello version to accept. If set to ProtocolVersion.SSL20Hello
@@ -61,10 +50,11 @@
     // fragment size
     int                 fragmentSize;
 
-    InputRecord() {
-        this.readCipher = CipherBox.NULL;
-        this.readAuthenticator = null;      // Please override this assignment.
-        this.helloVersion = ProtocolVersion.DEFAULT_HELLO;
+    InputRecord(HandshakeHash handshakeHash, SSLReadCipher readCipher) {
+        this.readCipher = readCipher;
+        this.helloVersion = ProtocolVersion.TLS10;
+        this.handshakeHash = handshakeHash;
+        this.isClosed = false;
         this.fragmentSize = Record.maxDataSize;
     }
 
@@ -76,37 +66,9 @@
         return helloVersion;
     }
 
-    /*
-     * Set instance for the computation of handshake hashes.
-     *
-     * For handshaking, we need to be able to hash every byte above the
-     * record marking layer.  This is where we're guaranteed to see those
-     * bytes, so this is where we can hash them ... especially in the
-     * case of hashing the initial V2 message!
-     */
-    void setHandshakeHash(HandshakeHash handshakeHash) {
-        if (handshakeHash != null) {
-            byte[] reserved = null;
-            if (this.handshakeHash != null) {
-                reserved = this.handshakeHash.getAllHandshakeMessages();
-            }
-            if ((reserved != null) && (reserved.length != 0)) {
-                handshakeHash.update(reserved, 0, reserved.length);
-
-               if (debug != null && Debug.isOn("data")) {
-                    Debug.printHex(
-                        "[reserved] handshake hash: len = " + reserved.length,
-                        reserved);
-               }
-            }
-        }
-
-        this.handshakeHash = handshakeHash;
-    }
-
     boolean seqNumIsHuge() {
-        return (readAuthenticator != null) &&
-                        readAuthenticator.seqNumIsHuge();
+        return (readCipher.authenticator != null) &&
+                        readCipher.authenticator.seqNumIsHuge();
     }
 
     boolean isEmpty() {
@@ -118,6 +80,11 @@
         // blank
     }
 
+    // apply to DTLS SSLEngine
+    void finishHandshake() {
+        // blank
+    }
+
     /**
      * Prevent any more data from being read into this record,
      * and flag the record as holding no data.
@@ -130,9 +97,12 @@
         }
     }
 
+    synchronized boolean isClosed() {
+        return isClosed;
+    }
+
     // apply to SSLSocket and SSLEngine
-    void changeReadCiphers(
-            Authenticator readAuthenticator, CipherBox readCipher) {
+    void changeReadCiphers(SSLReadCipher readCipher) {
 
         /*
          * Dispose of any intermediate state in the underlying cipher.
@@ -144,7 +114,6 @@
          */
         readCipher.dispose();
 
-        this.readAuthenticator = readAuthenticator;
         this.readCipher = readCipher;
     }
 
@@ -160,24 +129,22 @@
      * @return -1 if there are not enough bytes to tell (small header),
      */
     // apply to SSLEngine only
-    int bytesInCompletePacket(ByteBuffer buf) throws SSLException {
+    int bytesInCompletePacket(
+        ByteBuffer[] srcs, int srcsOffset, int srcsLength) throws IOException {
+
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    // apply to SSLSocket only
+    int bytesInCompletePacket() throws IOException {
         throw new UnsupportedOperationException();
     }
 
     // apply to SSLSocket only
-    int bytesInCompletePacket(InputStream is) throws IOException {
+    void setReceiverStream(InputStream inputStream) {
         throw new UnsupportedOperationException();
     }
 
-    /**
-     * Return true if the specified record protocol version is out of the
-     * range of the possible supported versions.
-     */
-    void checkRecordVersion(ProtocolVersion version,
-            boolean allowSSL20Hello) throws SSLException {
-        // blank
-    }
-
     // apply to DTLS SSLEngine only
     Plaintext acquirePlaintext()
             throws IOException, BadPaddingException {
@@ -186,17 +153,8 @@
 
     // read, decrypt and decompress the network record.
     //
-    // apply to SSLEngine only
-    Plaintext decode(ByteBuffer netData)
-            throws IOException, BadPaddingException {
-        throw new UnsupportedOperationException();
-    }
-
-    // apply to SSLSocket only
-    Plaintext decode(InputStream is, ByteBuffer destination)
-            throws IOException, BadPaddingException {
-        throw new UnsupportedOperationException();
-    }
+    abstract Plaintext[] decode(ByteBuffer[] srcs, int srcsOffset,
+            int srcsLength) throws IOException, BadPaddingException;
 
     // apply to SSLSocket only
     void setDeliverStream(OutputStream outputStream) {
@@ -216,9 +174,7 @@
 
     // Not apply to DTLS
     static ByteBuffer convertToClientHello(ByteBuffer packet) {
-
         int srcPos = packet.position();
-        int srcLim = packet.limit();
 
         byte firstByte = packet.get();
         byte secondByte = packet.get();
@@ -244,7 +200,7 @@
         //  1: length byte of ClientHello.session_id
         //  2: length bytes of ClientHello.cipher_suites
         //  2: empty ClientHello.compression_methods
-        int requiredSize = 48 + sessionIdLen + ((cipherSpecLen * 2 ) / 3 );
+        int requiredSize = 48 + sessionIdLen + ((cipherSpecLen * 2 ) / 3);
         byte[] converted = new byte[requiredSize];
 
         /*
@@ -252,7 +208,7 @@
          * that's now buffered up.  (Lengths are fixed up later).
          */
         // Note: need not to set the header actually.
-        converted[0] = ct_handshake;
+        converted[0] = ContentType.HANDSHAKE.id;
         converted[1] = majorVersion;
         converted[2] = minorVersion;
         // header [3..4] for handshake message length
@@ -372,237 +328,61 @@
         return ByteBuffer.wrap(converted, 5, pointer - 5);  // 5: header size
     }
 
-    static ByteBuffer decrypt(Authenticator authenticator, CipherBox box,
-            byte contentType, ByteBuffer bb) throws BadPaddingException {
-
-        return decrypt(authenticator, box, contentType, bb, null);
-    }
-
-    static ByteBuffer decrypt(Authenticator authenticator,
-            CipherBox box, byte contentType, ByteBuffer bb,
-            byte[] sequence) throws BadPaddingException {
-
-        BadPaddingException reservedBPE = null;
-        int tagLen =
-            (authenticator instanceof MAC) ? ((MAC)authenticator).MAClen() : 0;
-        int cipheredLength = bb.remaining();
-        int srcPos = bb.position();
-        if (!box.isNullCipher()) {
-            try {
-                // apply explicit nonce for AEAD/CBC cipher suites if needed
-                int nonceSize = box.applyExplicitNonce(
-                        authenticator, contentType, bb, sequence);
-
-                // decrypt the content
-                if (box.isAEADMode()) {
-                    // DON'T decrypt the nonce_explicit for AEAD mode
-                    bb.position(srcPos + nonceSize);
-                }   // The explicit IV for CBC mode can be decrypted.
-
-                // Note that the CipherBox.decrypt() does not change
-                // the capacity of the buffer.
-                box.decrypt(bb, tagLen);
-                // We don't actually remove the nonce.
-                bb.position(srcPos + nonceSize);
-            } catch (BadPaddingException bpe) {
-                // RFC 2246 states that decryption_failed should be used
-                // for this purpose. However, that allows certain attacks,
-                // so we just send bad record MAC. We also need to make
-                // sure to always check the MAC to avoid a timing attack
-                // for the same issue. See paper by Vaudenay et al and the
-                // update in RFC 4346/5246.
-                //
-                // Failover to message authentication code checking.
-                reservedBPE = bpe;
-            }
-        }
+    // Extract an SSL/(D)TLS record from the specified source buffers.
+    static ByteBuffer extract(
+            ByteBuffer[] buffers, int offset, int length, int headerSize) {
 
-        // Requires message authentication code for null, stream and block
-        // cipher suites.
-        if ((authenticator instanceof MAC) && (tagLen != 0)) {
-            MAC signer = (MAC)authenticator;
-            int contentLen = bb.remaining() - tagLen;
-
-            // Note that although it is not necessary, we run the same MAC
-            // computation and comparison on the payload for both stream
-            // cipher and CBC block cipher.
-            if (contentLen < 0) {
-                // negative data length, something is wrong
-                if (reservedBPE == null) {
-                    reservedBPE = new BadPaddingException("bad record");
+        boolean hasFullHeader = false;
+        int contentLen = -1;
+        for (int i = offset, j = 0;
+                i < (offset + length) && j < headerSize; i++) {
+            int remains = buffers[i].remaining();
+            int pos = buffers[i].position();
+            for (int k = 0; k < remains && j < headerSize; j++, k++) {
+                byte b = buffers[i].get(pos + k);
+                if (j == (headerSize - 2)) {
+                    contentLen = ((b & 0xFF) << 8);
+                } else if (j == (headerSize -1)) {
+                    contentLen |= (b & 0xFF);
+                    hasFullHeader = true;
+                    break;
                 }
-
-                // set offset of the dummy MAC
-                contentLen = cipheredLength - tagLen;
-                bb.limit(srcPos + cipheredLength);
-            }
-
-            // Run MAC computation and comparison on the payload.
-            //
-            // MAC data would be stripped off during the check.
-            if (checkMacTags(contentType, bb, signer, sequence, false)) {
-                if (reservedBPE == null) {
-                    reservedBPE = new BadPaddingException("bad record MAC");
-                }
-            }
-
-            // Run MAC computation and comparison on the remainder.
-            //
-            // It is only necessary for CBC block cipher.  It is used to get a
-            // constant time of MAC computation and comparison on each record.
-            if (box.isCBCMode()) {
-                int remainingLen = calculateRemainingLen(
-                                        signer, cipheredLength, contentLen);
-
-                // NOTE: remainingLen may be bigger (less than 1 block of the
-                // hash algorithm of the MAC) than the cipheredLength.
-                //
-                // Is it possible to use a static buffer, rather than allocate
-                // it dynamically?
-                remainingLen += signer.MAClen();
-                ByteBuffer temporary = ByteBuffer.allocate(remainingLen);
-
-                // Won't need to worry about the result on the remainder. And
-                // then we won't need to worry about what's actual data to
-                // check MAC tag on.  We start the check from the header of the
-                // buffer so that we don't need to construct a new byte buffer.
-                checkMacTags(contentType, temporary, signer, sequence, true);
             }
         }
 
-        // Is it a failover?
-        if (reservedBPE != null) {
-            throw reservedBPE;
-        }
-
-        return bb.slice();
-    }
-
-    /*
-     * Run MAC computation and comparison
-     *
-     */
-    private static boolean checkMacTags(byte contentType, ByteBuffer bb,
-            MAC signer, byte[] sequence, boolean isSimulated) {
-
-        int tagLen = signer.MAClen();
-        int position = bb.position();
-        int lim = bb.limit();
-        int macOffset = lim - tagLen;
-
-        bb.limit(macOffset);
-        byte[] hash = signer.compute(contentType, bb, sequence, isSimulated);
-        if (hash == null || tagLen != hash.length) {
-            // Something is wrong with MAC implementation.
-            throw new RuntimeException("Internal MAC error");
+        if (!hasFullHeader) {
+            throw new BufferUnderflowException();
         }
 
-        bb.position(macOffset);
-        bb.limit(lim);
-        try {
-            int[] results = compareMacTags(bb, hash);
-            return (results[0] != 0);
-        } finally {
-            // reset to the data
-            bb.position(position);
-            bb.limit(macOffset);
-        }
-    }
-
-    /*
-     * A constant-time comparison of the MAC tags.
-     *
-     * Please DON'T change the content of the ByteBuffer parameter!
-     */
-    private static int[] compareMacTags(ByteBuffer bb, byte[] tag) {
-
-        // An array of hits is used to prevent Hotspot optimization for
-        // the purpose of a constant-time check.
-        int[] results = {0, 0};     // {missed #, matched #}
-
-        // The caller ensures there are enough bytes available in the buffer.
-        // So we won't need to check the remaining of the buffer.
-        for (int i = 0; i < tag.length; i++) {
-            if (bb.get() != tag[i]) {
-                results[0]++;       // mismatched bytes
-            } else {
-                results[1]++;       // matched bytes
+        int packetLen = headerSize + contentLen;
+        int remains = 0;
+        for (int i = offset; i < offset + length; i++) {
+            remains += buffers[i].remaining();
+            if (remains >= packetLen) {
+                break;
             }
         }
 
-        return results;
-    }
-
-    /*
-     * Run MAC computation and comparison
-     *
-     * Please DON'T change the content of the byte buffer parameter!
-     */
-    private static boolean checkMacTags(byte contentType, byte[] buffer,
-            int offset, int contentLen, MAC signer, boolean isSimulated) {
-
-        int tagLen = signer.MAClen();
-        byte[] hash = signer.compute(
-                contentType, buffer, offset, contentLen, isSimulated);
-        if (hash == null || tagLen != hash.length) {
-            // Something is wrong with MAC implementation.
-            throw new RuntimeException("Internal MAC error");
+        if (remains < packetLen) {
+            throw new BufferUnderflowException();
         }
 
-        int[] results = compareMacTags(buffer, offset + contentLen, hash);
-        return (results[0] != 0);
-    }
-
-    /*
-     * A constant-time comparison of the MAC tags.
-     *
-     * Please DON'T change the content of the byte buffer parameter!
-     */
-    private static int[] compareMacTags(
-            byte[] buffer, int offset, byte[] tag) {
+        byte[] packet = new byte[packetLen];
+        int packetOffset = 0;
+        int packetSpaces = packetLen;
+        for (int i = offset; i < offset + length; i++) {
+            if (buffers[i].hasRemaining()) {
+                int len = Math.min(packetSpaces, buffers[i].remaining());
+                buffers[i].get(packet, packetOffset, len);
+                packetOffset += len;
+                packetSpaces -= len;
+            }
 
-        // An array of hits is used to prevent Hotspot optimization for
-        // the purpose of a constant-time check.
-        int[] results = {0, 0};    // {missed #, matched #}
-
-        // The caller ensures there are enough bytes available in the buffer.
-        // So we won't need to check the length of the buffer.
-        for (int i = 0; i < tag.length; i++) {
-            if (buffer[offset + i] != tag[i]) {
-                results[0]++;       // mismatched bytes
-            } else {
-                results[1]++;       // matched bytes
+            if (packetSpaces <= 0) {
+                break;
             }
         }
 
-        return results;
-    }
-
-    /*
-     * Calculate the length of a dummy buffer to run MAC computation
-     * and comparison on the remainder.
-     *
-     * The caller MUST ensure that the fullLen is not less than usedLen.
-     */
-    private static int calculateRemainingLen(
-            MAC signer, int fullLen, int usedLen) {
-
-        int blockLen = signer.hashBlockLen();
-        int minimalPaddingLen = signer.minimalPaddingLen();
-
-        // (blockLen - minimalPaddingLen) is the maximum message size of
-        // the last block of hash function operation. See FIPS 180-4, or
-        // MD5 specification.
-        fullLen += 13 - (blockLen - minimalPaddingLen);
-        usedLen += 13 - (blockLen - minimalPaddingLen);
-
-        // Note: fullLen is always not less than usedLen, and blockLen
-        // is always bigger than minimalPaddingLen, so we don't worry
-        // about negative values. 0x01 is added to the result to ensure
-        // that the return value is positive.  The extra one byte does
-        // not impact the overall MAC compression function evaluations.
-        return 0x01 + (int)(Math.ceil(fullLen/(1.0d * blockLen)) -
-                Math.ceil(usedLen/(1.0d * blockLen))) * blockLen;
+        return ByteBuffer.wrap(packet);
     }
 }
-