src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java
branchJDK-8145252-TLS13-branch
changeset 56542 56aaa6cb3693
parent 47216 71c04702a3d5
child 56660 66c803c3ce32
--- a/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java	Fri May 11 14:55:56 2018 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java	Fri May 11 15:53:12 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2015, 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
@@ -25,14 +25,13 @@
 
 package sun.security.ssl;
 
-import java.io.*;
-import java.nio.*;
-import java.util.*;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import javax.net.ssl.SSLHandshakeException;
 
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLHandshakeException;
-import sun.security.util.HexDumpEncoder;
-import static sun.security.ssl.Ciphertext.RecordType;
+import sun.security.ssl.SSLCipher.SSLWriteCipher;
+import sun.security.ssl.KeyUpdate.KeyUpdateMessage;
 
 /**
  * {@code OutputRecord} implementation for {@code SSLEngine}.
@@ -40,23 +39,22 @@
 final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
 
     private HandshakeFragment fragmenter = null;
-    private LinkedList<RecordMemo> alertMemos = new LinkedList<>();
     private boolean isTalkingToV2 = false;      // SSLv2Hello
     private ByteBuffer v2ClientHello = null;    // SSLv2Hello
 
     private boolean isCloseWaiting = false;
 
-    SSLEngineOutputRecord() {
-        this.writeAuthenticator = MAC.TLS_NULL;
+    SSLEngineOutputRecord(HandshakeHash handshakeHash) {
+        super(handshakeHash, SSLWriteCipher.nullTlsWriteCipher());
 
         this.packetSize = SSLRecord.maxRecordSize;
-        this.protocolVersion = ProtocolVersion.DEFAULT_TLS;
+        this.protocolVersion = ProtocolVersion.NONE;
     }
 
     @Override
     public synchronized void close() throws IOException {
         if (!isClosed) {
-            if (alertMemos != null && !alertMemos.isEmpty()) {
+            if (fragmenter != null && fragmenter.hasAlert()) {
                 isCloseWaiting = true;
             } else {
                 super.close();
@@ -66,19 +64,11 @@
 
     @Override
     void encodeAlert(byte level, byte description) throws IOException {
-        RecordMemo memo = new RecordMemo();
+        if (fragmenter == null) {
+           fragmenter = new HandshakeFragment();
+        }
 
-        memo.contentType = Record.ct_alert;
-        memo.majorVersion = protocolVersion.major;
-        memo.minorVersion = protocolVersion.minor;
-        memo.encodeCipher = writeCipher;
-        memo.encodeAuthenticator = writeAuthenticator;
-
-        memo.fragment = new byte[2];
-        memo.fragment[0] = level;
-        memo.fragment[1] = description;
-
-        alertMemos.add(memo);
+        fragmenter.queueUpAlert(level, description);
     }
 
     @Override
@@ -93,7 +83,7 @@
             firstMessage = false;
 
             if ((helloVersion == ProtocolVersion.SSL20Hello) &&
-                (source[offset] == HandshakeMessage.ht_client_hello) &&
+                (source[offset] == SSLHandshake.CLIENT_HELLO.id) &&
                                             //  5: recode header size
                 (source[offset + 4 + 2 + 32] == 0)) {
                                             // V3 session ID is empty
@@ -106,7 +96,7 @@
                         source, (offset + 4), (length - 4));
 
                 v2ClientHello.position(2);     // exclude the header
-                handshakeHash.update(v2ClientHello);
+                handshakeHash.deliver(v2ClientHello);
                 v2ClientHello.position(0);
 
                 return;
@@ -114,8 +104,8 @@
         }
 
         byte handshakeType = source[offset];
-        if (handshakeType != HandshakeMessage.ht_hello_request) {
-            handshakeHash.update(source, offset, length);
+        if (handshakeHash.isHashable(handshakeType)) {
+            handshakeHash.deliver(source, offset, length);
         }
 
         fragmenter.queueUpFragment(source, offset, length);
@@ -135,22 +125,43 @@
     }
 
     @Override
-    Ciphertext encode(ByteBuffer[] sources, int offset, int length,
+    Ciphertext encode(
+        ByteBuffer[] srcs, int srcsOffset, int srcsLength,
+        ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException {
+        return encode(srcs, srcsOffset, srcsLength, dsts[0]);
+    }
+
+    private Ciphertext encode(ByteBuffer[] sources, int offset, int length,
             ByteBuffer destination) throws IOException {
 
-        if (writeAuthenticator.seqNumOverflow()) {
-            if (debug != null && Debug.isOn("ssl")) {
-                System.out.println(Thread.currentThread().getName() +
-                    ", sequence number extremely close to overflow " +
+        if (writeCipher.authenticator.seqNumOverflow()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                SSLLogger.fine(
+                    "sequence number extremely close to overflow " +
                     "(2^64-1 packets). Closing connection.");
             }
 
             throw new SSLHandshakeException("sequence number overflow");
         }
 
-        int macLen = 0;
-        if (writeAuthenticator instanceof MAC) {
-            macLen = ((MAC)writeAuthenticator).MAClen();
+        // Don't process the incoming record until all of the
+        // buffered records get handled.
+        Ciphertext ct = acquireCiphertext(destination);
+        if (ct != null) {
+            return ct;
+        }
+
+        if (sources == null || sources.length == 0) {
+            return null;
+        }
+
+        int srcsRemains = 0;
+        for (int i = offset; i < offset + length; i++) {
+            srcsRemains += sources[i].remaining();
+        }
+
+        if (srcsRemains == 0) {
+            return null;
         }
 
         int dstLim = destination.limit();
@@ -169,8 +180,8 @@
                 needMorePayload = false;
 
                 if (packetLeftSize > 0) {
-                    fragLen = writeCipher.calculateFragmentSize(
-                            packetLeftSize, macLen, headerSize);
+                    fragLen =writeCipher.calculateFragmentSize(
+                            packetLeftSize, headerSize);
 
                     fragLen = Math.min(fragLen, Record.maxDataSize);
                 } else {
@@ -208,26 +219,24 @@
             destination.limit(destination.position());
             destination.position(dstContent);
 
-            if ((debug != null) && Debug.isOn("record")) {
-                System.out.println(Thread.currentThread().getName() +
-                        ", WRITE: " + protocolVersion + " " +
-                        Record.contentName(Record.ct_application_data) +
+            if (SSLLogger.isOn && SSLLogger.isOn("record")) {
+                SSLLogger.fine(
+                        "WRITE: " + protocolVersion + " " +
+                        ContentType.APPLICATION_DATA.name +
                         ", length = " + destination.remaining());
             }
 
             // Encrypt the fragment and wrap up a record.
-            recordSN = encrypt(writeAuthenticator, writeCipher,
-                    Record.ct_application_data, destination,
+            recordSN = encrypt(writeCipher,
+                    ContentType.APPLICATION_DATA.id, destination,
                     dstPos, dstLim, headerSize,
-                    protocolVersion, false);
+                    protocolVersion);
 
-            if ((debug != null) && Debug.isOn("packet")) {
+            if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
                 ByteBuffer temporary = destination.duplicate();
                 temporary.limit(temporary.position());
                 temporary.position(dstPos);
-                Debug.printHex(
-                        "[Raw write]: length = " + temporary.remaining(),
-                        temporary);
+                SSLLogger.fine("Raw write", temporary);
             }
 
             packetLeftSize -= destination.position() - dstPos;
@@ -238,103 +247,60 @@
             if (isFirstAppOutputRecord) {
                 isFirstAppOutputRecord = false;
             }
+
+            if (writeCipher.atKeyLimit()) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.fine("KeyUpdate: triggered");
+                }
+
+                PostHandshakeContext p = new PostHandshakeContext(tc);
+                KeyUpdate.handshakeProducer.produce(p,
+                        new KeyUpdateMessage(p, KeyUpdateMessage.REQUSTED));
+            }
         }
 
-        return new Ciphertext(RecordType.RECORD_APPLICATION_DATA, recordSN);
+        return new Ciphertext(ContentType.APPLICATION_DATA.id,
+                SSLHandshake.NOT_APPLICABLE.id, recordSN);
     }
 
-    @Override
-    Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException {
+    private Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException {
         if (isTalkingToV2) {              // SSLv2Hello
             // We don't support SSLv2.  Send an SSLv2 error message
             // so that the connection can be closed gracefully.
             //
             // Please don't change the limit of the destination buffer.
             destination.put(SSLRecord.v2NoCipher);
-            if (debug != null && Debug.isOn("packet")) {
-                Debug.printHex(
-                        "[Raw write]: length = " + SSLRecord.v2NoCipher.length,
-                        SSLRecord.v2NoCipher);
+            if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
+                SSLLogger.fine("Raw write", SSLRecord.v2NoCipher);
             }
 
             isTalkingToV2 = false;
 
-            return new Ciphertext(RecordType.RECORD_ALERT, -1L);
+            return new Ciphertext(ContentType.ALERT.id,
+                    SSLHandshake.NOT_APPLICABLE.id, -1L);
         }
 
         if (v2ClientHello != null) {
             // deliver the SSLv2 format ClientHello message
             //
             // Please don't change the limit of the destination buffer.
-            if (debug != null) {
-                if (Debug.isOn("record")) {
-                     System.out.println(Thread.currentThread().getName() +
+            if (SSLLogger.isOn) {
+                if (SSLLogger.isOn("record")) {
+                     SSLLogger.fine(Thread.currentThread().getName() +
                             ", WRITE: SSLv2 ClientHello message" +
                             ", length = " + v2ClientHello.remaining());
                 }
 
-                if (Debug.isOn("packet")) {
-                    Debug.printHex(
-                        "[Raw write]: length = " + v2ClientHello.remaining(),
-                        v2ClientHello);
+                if (SSLLogger.isOn("packet")) {
+                    SSLLogger.fine("Raw write", v2ClientHello);
                 }
             }
 
             destination.put(v2ClientHello);
             v2ClientHello = null;
 
-            return new Ciphertext(RecordType.RECORD_CLIENT_HELLO, -1L);
-        }
-
-        if (alertMemos != null && !alertMemos.isEmpty()) {
-            RecordMemo memo = alertMemos.pop();
-
-            int macLen = 0;
-            if (memo.encodeAuthenticator instanceof MAC) {
-                macLen = ((MAC)memo.encodeAuthenticator).MAClen();
-            }
-
-            int dstPos = destination.position();
-            int dstLim = destination.limit();
-            int dstContent = dstPos + headerSize +
-                                writeCipher.getExplicitNonceSize();
-            destination.position(dstContent);
-
-            destination.put(memo.fragment);
-
-            destination.limit(destination.position());
-            destination.position(dstContent);
-
-            if ((debug != null) && Debug.isOn("record")) {
-                System.out.println(Thread.currentThread().getName() +
-                        ", WRITE: " + protocolVersion + " " +
-                        Record.contentName(Record.ct_alert) +
-                        ", length = " + destination.remaining());
-            }
-
-            // Encrypt the fragment and wrap up a record.
-            long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher,
-                    Record.ct_alert, destination, dstPos, dstLim, headerSize,
-                    ProtocolVersion.valueOf(memo.majorVersion,
-                            memo.minorVersion), false);
-
-            if ((debug != null) && Debug.isOn("packet")) {
-                ByteBuffer temporary = destination.duplicate();
-                temporary.limit(temporary.position());
-                temporary.position(dstPos);
-                Debug.printHex(
-                        "[Raw write]: length = " + temporary.remaining(),
-                        temporary);
-            }
-
-            // remain the limit unchanged
-            destination.limit(dstLim);
-
-            if (isCloseWaiting && (memo.contentType == Record.ct_alert)) {
-                isCloseWaiting = true;
-                close();
-            }
-            return new Ciphertext(RecordType.RECORD_ALERT, recordSN);
+            return new Ciphertext(ContentType.HANDSHAKE.id,
+                   SSLHandshake.CLIENT_HELLO.id, -1L);
         }
 
         if (fragmenter != null) {
@@ -347,8 +313,7 @@
     @Override
     boolean isEmpty() {
         return (!isTalkingToV2) && (v2ClientHello == null) &&
-                ((fragmenter == null) || fragmenter.isEmpty()) &&
-                ((alertMemos == null) || alertMemos.isEmpty());
+                ((fragmenter == null) || fragmenter.isEmpty());
     }
 
     // buffered record fragment
@@ -356,8 +321,7 @@
         byte            contentType;
         byte            majorVersion;
         byte            minorVersion;
-        CipherBox       encodeCipher;
-        Authenticator   encodeAuthenticator;
+        SSLWriteCipher  encodeCipher;
 
         byte[]          fragment;
     }
@@ -372,14 +336,12 @@
 
         void queueUpFragment(byte[] source,
                 int offset, int length) throws IOException {
-
             HandshakeMemo memo = new HandshakeMemo();
 
-            memo.contentType = Record.ct_handshake;
+            memo.contentType = ContentType.HANDSHAKE.id;
             memo.majorVersion = protocolVersion.major;  // kick start version?
             memo.minorVersion = protocolVersion.minor;
             memo.encodeCipher = writeCipher;
-            memo.encodeAuthenticator = writeAuthenticator;
 
             memo.handshakeType = source[offset];
             memo.acquireOffset = 0;
@@ -394,11 +356,10 @@
         void queueUpChangeCipherSpec() {
             RecordMemo memo = new RecordMemo();
 
-            memo.contentType = Record.ct_change_cipher_spec;
+            memo.contentType = ContentType.CHANGE_CIPHER_SPEC.id;
             memo.majorVersion = protocolVersion.major;
             memo.minorVersion = protocolVersion.minor;
             memo.encodeCipher = writeCipher;
-            memo.encodeAuthenticator = writeAuthenticator;
 
             memo.fragment = new byte[1];
             memo.fragment[0] = 1;
@@ -406,6 +367,21 @@
             handshakeMemos.add(memo);
         }
 
+        void queueUpAlert(byte level, byte description) {
+            RecordMemo memo = new RecordMemo();
+
+            memo.contentType = ContentType.ALERT.id;
+            memo.majorVersion = protocolVersion.major;
+            memo.minorVersion = protocolVersion.minor;
+            memo.encodeCipher = writeCipher;
+
+            memo.fragment = new byte[2];
+            memo.fragment[0] = level;
+            memo.fragment[1] = description;
+
+            handshakeMemos.add(memo);
+        }
+
         Ciphertext acquireCiphertext(ByteBuffer dstBuf) throws IOException {
             if (isEmpty()) {
                 return null;
@@ -413,22 +389,17 @@
 
             RecordMemo memo = handshakeMemos.getFirst();
             HandshakeMemo hsMemo = null;
-            if (memo.contentType == Record.ct_handshake) {
+            if (memo.contentType == ContentType.HANDSHAKE.id) {
                 hsMemo = (HandshakeMemo)memo;
             }
 
-            int macLen = 0;
-            if (memo.encodeAuthenticator instanceof MAC) {
-                macLen = ((MAC)memo.encodeAuthenticator).MAClen();
-            }
-
             // ChangeCipherSpec message is pretty small.  Don't worry about
             // the fragmentation of ChangeCipherSpec record.
             int fragLen;
             if (packetSize > 0) {
                 fragLen = Math.min(maxRecordSize, packetSize);
                 fragLen = memo.encodeCipher.calculateFragmentSize(
-                        fragLen, macLen, headerSize);
+                        fragLen, headerSize);
             } else {
                 fragLen = Record.maxDataSize;
             }
@@ -474,11 +445,12 @@
                                  !handshakeMemos.isEmpty()) {
 
                             // look for the next buffered record fragment
-                            RecordMemo reMemo = handshakeMemos.getFirst();
-                            if (reMemo.contentType == Record.ct_handshake) {
-                                hsMemo = (HandshakeMemo)reMemo;
+                            RecordMemo rm = handshakeMemos.getFirst();
+                            if (rm.contentType == ContentType.HANDSHAKE.id &&
+                                    rm.encodeCipher == hsMemo.encodeCipher) {
+                                hsMemo = (HandshakeMemo)rm;
                             } else {
-                                // not handshake message, break the loop
+                                // not of the flight, break the loop
                                 break;
                             }
                         }
@@ -498,27 +470,26 @@
             dstBuf.limit(dstBuf.position());
             dstBuf.position(dstContent);
 
-            if ((debug != null) && Debug.isOn("record")) {
-                System.out.println(Thread.currentThread().getName() +
-                        ", WRITE: " + protocolVersion + " " +
-                        Record.contentName(memo.contentType) +
+            if (SSLLogger.isOn && SSLLogger.isOn("record")) {
+                SSLLogger.fine(
+                        "WRITE: " + protocolVersion + " " +
+                        ContentType.nameOf(memo.contentType) +
                         ", length = " + dstBuf.remaining());
             }
 
             // Encrypt the fragment and wrap up a record.
-            long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher,
+            long recordSN = encrypt(
+                    memo.encodeCipher,
                     memo.contentType, dstBuf,
                     dstPos, dstLim, headerSize,
                     ProtocolVersion.valueOf(memo.majorVersion,
-                            memo.minorVersion), false);
+                            memo.minorVersion));
 
-            if ((debug != null) && Debug.isOn("packet")) {
+            if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
                 ByteBuffer temporary = dstBuf.duplicate();
                 temporary.limit(temporary.position());
                 temporary.position(dstPos);
-                Debug.printHex(
-                        "[Raw write]: length = " + temporary.remaining(),
-                        temporary);
+                SSLLogger.fine("Raw write", temporary);
             }
 
             // remain the limit unchanged
@@ -526,17 +497,32 @@
 
             // Reset the fragmentation offset.
             if (hsMemo != null) {
-                return new Ciphertext(RecordType.valueOf(
-                        hsMemo.contentType, hsMemo.handshakeType), recordSN);
+                return new Ciphertext(hsMemo.contentType,
+                        hsMemo.handshakeType, recordSN);
             } else {
-                return new Ciphertext(
-                        RecordType.RECORD_CHANGE_CIPHER_SPEC, recordSN);
+                if (isCloseWaiting &&
+                        memo.contentType == ContentType.ALERT.id) {
+                    close();
+                }
+
+                return new Ciphertext(memo.contentType,
+                        SSLHandshake.NOT_APPLICABLE.id, recordSN);
             }
         }
 
         boolean isEmpty() {
             return handshakeMemos.isEmpty();
         }
+
+        boolean hasAlert() {
+            for (RecordMemo memo : handshakeMemos) {
+                if (memo.contentType ==  ContentType.ALERT.id) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
     }
 
     /*