src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java

Print this page

        

@@ -139,15 +139,24 @@
      * server certificate change in renegotiation is disabled.
      */
     private final static boolean allowUnsafeServerCertChange =
         Debug.getBooleanProperty("jdk.tls.allowUnsafeServerCertChange", false);
 
+    // To switch off the max_fragment_length extension.
+    private final static boolean enableMFLExtension =
+            Debug.getBooleanProperty("jsse.enableMFLExtension", false);
+
     private List<SNIServerName> requestedServerNames =
             Collections.<SNIServerName>emptyList();
 
+    // maximum fragment length
+    private int requestedMFLength = -1;     // -1: no fragment length limit
+
     private boolean serverNamesAccepted = false;
 
+    private ClientHello initialClientHelloMsg = null;   // DTLS only
+
     /*
      * the reserved server certificate chain in previous handshaking
      *
      * The server certificate chain is only reserved if the previous
      * handshake is a session-resumption abbreviated initial handshake.

@@ -170,15 +179,16 @@
 
     ClientHandshaker(SSLEngineImpl engine, SSLContextImpl context,
             ProtocolList enabledProtocols,
             ProtocolVersion activeProtocolVersion,
             boolean isInitialHandshake, boolean secureRenegotiation,
-            byte[] clientVerifyData, byte[] serverVerifyData) {
+            byte[] clientVerifyData, byte[] serverVerifyData,
+            boolean isDTLS) {
 
         super(engine, context, enabledProtocols, true, true,
             activeProtocolVersion, isInitialHandshake, secureRenegotiation,
-            clientVerifyData, serverVerifyData);
+            clientVerifyData, serverVerifyData, isDTLS);
     }
 
     /*
      * This routine handles all the client side handshake messages, one at
      * a time.  Given the message type (and in some cases the pending cipher

@@ -189,33 +199,51 @@
      * is processed, and writes responses as needed using the connection
      * in the constructor.
      */
     @Override
     void processMessage(byte type, int messageLen) throws IOException {
-        if (state >= type
-                && (type != HandshakeMessage.ht_hello_request)) {
-            throw new SSLProtocolException(
-                    "Handshake message sequence violation, " + type);
-        }
+        // check the handshake state
+        handshakeState.check(type);
 
         switch (type) {
         case HandshakeMessage.ht_hello_request:
-            this.serverHelloRequest(new HelloRequest(input));
+            HelloRequest helloRequest = new HelloRequest(input);
+            handshakeState.update(helloRequest, resumingSession);
+            this.serverHelloRequest(helloRequest);
             break;
 
+        case HandshakeMessage.ht_hello_verify_request:
+            if (!isDTLS) {
+                throw new SSLProtocolException(
+                    "hello_verify_request is not a SSL/TLS handshake message");
+            }
+
+            HelloVerifyRequest helloVerifyRequest =
+                        new HelloVerifyRequest(input, messageLen);
+            handshakeState.update(helloVerifyRequest, resumingSession);
+            this.helloVerifyRequest(helloVerifyRequest);
+            break;
+
         case HandshakeMessage.ht_server_hello:
-            this.serverHello(new ServerHello(input, messageLen));
+            ServerHello serverHello = new ServerHello(input, messageLen);
+            this.serverHello(serverHello);
+
+            // This handshake state update needs the resumingSession value
+            // set by serverHello().
+            handshakeState.update(serverHello, resumingSession);
             break;
 
         case HandshakeMessage.ht_certificate:
             if (keyExchange == K_DH_ANON || keyExchange == K_ECDH_ANON
                     || keyExchange == K_KRB5 || keyExchange == K_KRB5_EXPORT) {
                 fatalSE(Alerts.alert_unexpected_message,
                     "unexpected server cert chain");
                 // NOTREACHED
             }
-            this.serverCertificate(new CertificateMsg(input));
+            CertificateMsg certificateMsg = new CertificateMsg(input);
+            handshakeState.update(certificateMsg, resumingSession);
+            this.serverCertificate(certificateMsg);
             serverKey =
                 session.getPeerCertificates()[0].getPublicKey();
             break;
 
         case HandshakeMessage.ht_server_key_exchange:

@@ -247,45 +275,56 @@
                         " when the public key in the server certificate" +
                         " is less than or equal to 512 bits in length");
                 }
 
                 try {
-                    this.serverKeyExchange(new RSA_ServerKeyExchange(input));
+                    RSA_ServerKeyExchange rsaSrvKeyExchange =
+                                    new RSA_ServerKeyExchange(input);
+                    handshakeState.update(rsaSrvKeyExchange, resumingSession);
+                    this.serverKeyExchange(rsaSrvKeyExchange);
                 } catch (GeneralSecurityException e) {
-                    throwSSLException("Server key", e);
+                    throw new SSLException("Server key", e);
                 }
                 break;
             case K_DH_ANON:
                 try {
-                    this.serverKeyExchange(new DH_ServerKeyExchange(
-                                                input, protocolVersion));
+                    DH_ServerKeyExchange dhSrvKeyExchange =
+                            new DH_ServerKeyExchange(input, protocolVersion);
+                    handshakeState.update(dhSrvKeyExchange, resumingSession);
+                    this.serverKeyExchange(dhSrvKeyExchange);
                 } catch (GeneralSecurityException e) {
-                    throwSSLException("Server key", e);
+                    throw new SSLException("Server key", e);
                 }
                 break;
             case K_DHE_DSS:
             case K_DHE_RSA:
                 try {
-                    this.serverKeyExchange(new DH_ServerKeyExchange(
+                    DH_ServerKeyExchange dhSrvKeyExchange =
+                        new DH_ServerKeyExchange(
                         input, serverKey,
                         clnt_random.random_bytes, svr_random.random_bytes,
                         messageLen,
-                        localSupportedSignAlgs, protocolVersion));
+                            localSupportedSignAlgs, protocolVersion);
+                    handshakeState.update(dhSrvKeyExchange, resumingSession);
+                    this.serverKeyExchange(dhSrvKeyExchange);
                 } catch (GeneralSecurityException e) {
-                    throwSSLException("Server key", e);
+                    throw new SSLException("Server key", e);
                 }
                 break;
             case K_ECDHE_ECDSA:
             case K_ECDHE_RSA:
             case K_ECDH_ANON:
                 try {
-                    this.serverKeyExchange(new ECDH_ServerKeyExchange
+                    ECDH_ServerKeyExchange ecdhSrvKeyExchange =
+                        new ECDH_ServerKeyExchange
                         (input, serverKey, clnt_random.random_bytes,
                         svr_random.random_bytes,
-                        localSupportedSignAlgs, protocolVersion));
+                            localSupportedSignAlgs, protocolVersion);
+                    handshakeState.update(ecdhSrvKeyExchange, resumingSession);
+                    this.serverKeyExchange(ecdhSrvKeyExchange);
                 } catch (GeneralSecurityException e) {
-                    throwSSLException("Server key", e);
+                    throw new SSLException("Server key", e);
                 }
                 break;
             case K_RSA:
             case K_DH_RSA:
             case K_DH_DSS:

@@ -318,12 +357,13 @@
             }
             certRequest = new CertificateRequest(input, protocolVersion);
             if (debug != null && Debug.isOn("handshake")) {
                 certRequest.print(System.out);
             }
+            handshakeState.update(certRequest, resumingSession);
 
-            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            if (protocolVersion.useTLS12PlusSpec()) {
                 Collection<SignatureAndHashAlgorithm> peerSignAlgs =
                                         certRequest.getSignAlgorithms();
                 if (peerSignAlgs == null || peerSignAlgs.isEmpty()) {
                     throw new SSLHandshakeException(
                         "No peer supported signature algorithms");

@@ -343,38 +383,29 @@
             }
 
             break;
 
         case HandshakeMessage.ht_server_hello_done:
-            this.serverHelloDone(new ServerHelloDone(input));
+            ServerHelloDone serverHelloDone = new ServerHelloDone(input);
+            handshakeState.update(serverHelloDone, resumingSession);
+            this.serverHelloDone(serverHelloDone);
+
             break;
 
         case HandshakeMessage.ht_finished:
-            // A ChangeCipherSpec record must have been received prior to
-            // reception of the Finished message (RFC 5246, 7.4.9).
-            if (!receivedChangeCipherSpec()) {
-                fatalSE(Alerts.alert_handshake_failure,
-                    "Received Finished message before ChangeCipherSpec");
-            }
+            Finished serverFinished =
+                    new Finished(protocolVersion, input, cipherSuite);
+            handshakeState.update(serverFinished, resumingSession);
+            this.serverFinished(serverFinished);
 
-            this.serverFinished(
-                new Finished(protocolVersion, input, cipherSuite));
             break;
 
         default:
             throw new SSLProtocolException(
                 "Illegal client handshake msg, " + type);
         }
-
-        //
-        // Move state machine forward if the message handling
-        // code didn't already do so
-        //
-        if (state < type) {
-            state = type;
         }
-    }
 
     /*
      * Used by the server to kickstart negotiations -- this requests a
      * "client hello" to renegotiate current cipher specs (e.g. maybe lots
      * of data has been encrypted with the same keys, or the server needs

@@ -387,14 +418,14 @@
 
         //
         // Could be (e.g. at connection setup) that we already
         // sent the "client hello" but the server's not seen it.
         //
-        if (state < HandshakeMessage.ht_client_hello) {
+        if (!clientHelloDelivered) {
             if (!secureRenegotiation && !allowUnsafeRenegotiation) {
                 // renegotiation is not allowed.
-                if (activeProtocolVersion.v >= ProtocolVersion.TLS10.v) {
+                if (activeProtocolVersion.useTLS10PlusSpec()) {
                     // response with a no_renegotiation warning,
                     warningSE(Alerts.alert_no_renegotiation);
 
                     // invalidate the handshake so that the caller can
                     // dispose this object.

@@ -426,11 +457,34 @@
                 kickstart();
             }
         }
     }
 
+    private void helloVerifyRequest(
+            HelloVerifyRequest mesg) throws IOException {
 
+        if (debug != null && Debug.isOn("handshake")) {
+            mesg.print(System.out);
+        }
+
+        //
+        // Note that HelloVerifyRequest.server_version is used solely to
+        // indicate packet formatting, and not as part of version negotiation.
+        // Need not to check version values match for HelloVerifyRequest
+        // message.
+        //
+        initialClientHelloMsg.cookie = mesg.cookie.clone();
+
+        if (debug != null && Debug.isOn("handshake")) {
+            initialClientHelloMsg.print(System.out);
+        }
+
+        // deliver the ClientHello message with cookie
+        initialClientHelloMsg.write(output);
+        handshakeState.update(initialClientHelloMsg, resumingSession);
+    }
+
     /*
      * Server chooses session parameters given options created by the
      * client -- basically, cipher options, session id, and someday a
      * set of compression options.
      *

@@ -439,10 +493,13 @@
      * can resume the pre-existing session we asked resume.  The other
      * is a more expensive "full" handshake, with key exchange and
      * probably authentication getting done.
      */
     private void serverHello(ServerHello mesg) throws IOException {
+        // Dispose the reserved ClientHello message (if exists).
+        initialClientHelloMsg = null;
+
         serverKeyExchangeReceived = false;
         if (debug != null && Debug.isOn("handshake")) {
             mesg.print(System.out);
         }
 

@@ -534,11 +591,11 @@
             fatalSE(Alerts.alert_illegal_parameter,
                 "Server selected improper ciphersuite " + mesg.cipherSuite);
         }
 
         setCipherSuite(mesg.cipherSuite);
-        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+        if (protocolVersion.useTLS12PlusSpec()) {
             handshakeHash.setFinishedAlg(cipherSuite.prfAlg.getPRFHashAlg());
         }
 
         if (mesg.compression_method != 0) {
             fatalSE(Alerts.alert_illegal_parameter,

@@ -609,13 +666,12 @@
                         throw new SSLProtocolException
                             ("Server resumed session with no subject");
                     }
                 }
 
-                // looks fine; resume it, and update the state machine.
+                // looks fine; resume it.
                 resumingSession = true;
-                state = HandshakeMessage.ht_finished - 1;
                 calculateConnectionKeys(session.getMasterSecret());
                 if (debug != null && Debug.isOn("session")) {
                     System.out.println("%% Server resumed " + session);
                 }
             } else {

@@ -625,10 +681,28 @@
                     throw new SSLException("New session creation is disabled");
                 }
             }
         }
 
+        // check the "max_fragment_length" extension
+        MaxFragmentLengthExtension maxFragLenExt = (MaxFragmentLengthExtension)
+                mesg.extensions.get(ExtensionType.EXT_MAX_FRAGMENT_LENGTH);
+        if (maxFragLenExt != null) {
+            if ((requestedMFLength == -1) ||
+                    maxFragLenExt.getMaxFragLen() != requestedMFLength) {
+                // If the client did not request this extension, or the
+                // response value is different from the length it requested,
+                // abort the handshake with a fatal illegal_parameter alert.
+                fatalSE(Alerts.alert_illegal_parameter,
+                        "Failed to negotiate the max_fragment_length");
+            }
+        } else if (!resumingSession) {
+            // no "max_fragment_length" extension
+            requestedMFLength = -1;
+        }   // Otherwise, using the value negotiated during the original
+            // session initiation 
+
         if (resumingSession && session != null) {
             setHandshakeSessionSE(session);
             // Reserve the handshake state if this is a session-resumption
             // abbreviated initial handshake.
             if (isInitialHandshake) {

@@ -655,10 +729,12 @@
         // Create a new session, we need to do the full handshake
         session = new SSLSessionImpl(protocolVersion, cipherSuite,
                             getLocalSupportedSignAlgs(),
                             mesg.sessionId, getHostSE(), getPortSE());
         session.setRequestedServerNames(requestedServerNames);
+        session.setNegotiatedMaxFragSize(requestedMFLength);
+        session.setMaximumPacketSize(maximumPacketSize);
         setHandshakeSessionSE(session);
         if (debug != null && Debug.isOn("handshake")) {
             System.out.println("** " + cipherSuite);
         }
     }

@@ -679,11 +755,10 @@
             // NOTREACHED
         }
         ephemeralServerKey = mesg.getPublicKey();
     }
 
-
     /*
      * Diffie-Hellman key exchange.  We save the server public key and
      * our own D-H algorithm object so we can defer key calculations
      * until after we've sent the client key exchange message (which
      * gives client and server some useful parallelism).

@@ -714,17 +789,10 @@
      */
     private void serverHelloDone(ServerHelloDone mesg) throws IOException {
         if (debug != null && Debug.isOn("handshake")) {
             mesg.print(System.out);
         }
-        /*
-         * Always make sure the input has been digested before we
-         * start emitting data, to ensure the hashes are correctly
-         * computed for the Finished and CertificateVerify messages
-         * which we send (here).
-         */
-        input.digestNow();
 
         /*
          * FIRST ... if requested, send an appropriate Certificate chain
          * to authenticate the client, and remember the associated private
          * key to sign the CertificateVerify message.

@@ -815,11 +883,11 @@
                 //
                 // No appropriate cert was found ... report this to the
                 // server.  For SSLv3, send the no_certificate alert;
                 // TLS uses an empty cert chain instead.
                 //
-                if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+                if (protocolVersion.useTLS10PlusSpec()) {
                     m1 = new CertificateMsg(new X509Certificate [0]);
                 } else {
                     warningSE(Alerts.alert_no_certificate);
                 }
                 if (debug != null && Debug.isOn("handshake")) {

@@ -835,10 +903,11 @@
             if (m1 != null) {
                 if (debug != null && Debug.isOn("handshake")) {
                     m1.print(System.out);
                 }
                 m1.write(output);
+                handshakeState.update(m1, resumingSession);
             }
         }
 
         /*
          * SECOND ... send the client key exchange message.  The

@@ -998,22 +1067,20 @@
         }
         if (debug != null && Debug.isOn("handshake")) {
             m2.print(System.out);
         }
         m2.write(output);
+        handshakeState.update(m2, resumingSession);
 
-
         /*
          * THIRD, send a "change_cipher_spec" record followed by the
          * "Finished" message.  We flush the messages we've queued up, to
          * get concurrency between client and server.  The concurrency is
          * useful as we calculate the master secret, which is needed both
          * to compute the "Finished" message, and to compute the keys used
          * to protect all records following the change_cipher_spec.
          */
-
-        output.doHashes();
         output.flush();
 
         /*
          * We deferred calculating the master secret and this connection's
          * keying data; we do it now.  Deferring this calculation is good

@@ -1067,11 +1134,11 @@
          */
         if (signingKey != null) {
             CertificateVerify m3;
             try {
                 SignatureAndHashAlgorithm preferableSignatureAlgorithm = null;
-                if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+                if (protocolVersion.useTLS12PlusSpec()) {
                     preferableSignatureAlgorithm =
                         SignatureAndHashAlgorithm.getPreferableAlgorithm(
                             peerSupportedSignAlgs, signingKey.getAlgorithm(),
                             signingKey);
 

@@ -1101,17 +1168,21 @@
             }
             if (debug != null && Debug.isOn("handshake")) {
                 m3.print(System.out);
             }
             m3.write(output);
-            output.doHashes();
+            handshakeState.update(m3, resumingSession);
+            output.flush();
         }
 
         /*
          * OK, that's that!
          */
         sendChangeCipherAndFinish(false);
+
+        // expecting the final ChangeCipherSpec and Finished messages
+        expectingFinishFlightSE();
     }
 
 
     /*
      * "Finished" is the last handshake message sent.  If we got this

@@ -1156,12 +1227,13 @@
          * In any case, update the session cache.  We're done handshaking,
          * so there are no threats any more associated with partially
          * completed handshakes.
          */
         if (resumingSession) {
-            input.digestNow();
             sendChangeCipherAndFinish(true);
+        } else {
+            handshakeFinished = true;
         }
         session.setLastAccessedTime(System.currentTimeMillis());
 
         if (!resumingSession) {
             if (session.isRejoinable()) {

@@ -1186,10 +1258,14 @@
      * the short one, we've already seen the server's Finished; in the
      * long one, we wait for it now.
      */
     private void sendChangeCipherAndFinish(boolean finishedTag)
             throws IOException {
+
+        // Reload if this message has been reserved.
+        handshakeHash.reload();
+
         Finished mesg = new Finished(protocolVersion, handshakeHash,
             Finished.CLIENT, session.getMasterSecret(), cipherSuite);
 
         /*
          * Send the change_cipher_spec message, then the Finished message

@@ -1203,17 +1279,10 @@
          * save client verify data for secure renegotiation
          */
         if (secureRenegotiation) {
             clientVerifyData = mesg.getVerifyData();
         }
-
-        /*
-         * Update state machine so server MUST send 'finished' next.
-         * (In "long" handshake case; in short case, we're responding
-         * to its message.)
-         */
-        state = HandshakeMessage.ht_finished - 1;
     }
 
 
     /*
      * Returns a ClientHello message to kickstart renegotiations

@@ -1359,14 +1428,14 @@
         // }
 
         // create the ClientHello message
         ClientHello clientHelloMessage = new ClientHello(
                 sslContext.getSecureRandom(), maxProtocolVersion,
-                sessionId, cipherSuites);
+                sessionId, cipherSuites, isDTLS);
 
         // add signature_algorithm extension
-        if (maxProtocolVersion.v >= ProtocolVersion.TLS12.v) {
+        if (maxProtocolVersion.useTLS12PlusSpec()) {
             // we will always send the signature_algorithm extension
             Collection<SignatureAndHashAlgorithm> localSignAlgs =
                                                 getLocalSupportedSignAlgs();
             if (localSignAlgs.isEmpty()) {
                 throw new SSLHandshakeException(

@@ -1387,10 +1456,41 @@
             if (!requestedServerNames.isEmpty()) {
                 clientHelloMessage.addSNIExtension(requestedServerNames);
             }
         }
 
+        // add max_fragment_length extension
+        if (enableMFLExtension) {
+            if (session != null) {
+                // The same extension should be sent for resumption.
+                requestedMFLength = session.getNegotiatedMaxFragSize();
+            } else if (maximumPacketSize != 0) {
+                // Maybe we can calculate the fragment size more accurate
+                // by condering the enabled cipher suites in the future.
+                requestedMFLength = maximumPacketSize;
+                if (isDTLS) {
+                    requestedMFLength -= DTLSRecord.maxPlaintextPlusSize;
+                } else {
+                    requestedMFLength -= SSLRecord.maxPlaintextPlusSize;
+                }
+            } else {
+                // Need no max_fragment_length extension.
+                requestedMFLength = -1;
+            }
+
+            if ((requestedMFLength > 0) &&
+                MaxFragmentLengthExtension.needFragLenNego(requestedMFLength)) {
+
+                requestedMFLength =
+                        MaxFragmentLengthExtension.getValidMaxFragLen(
+                                                        requestedMFLength);
+                clientHelloMessage.addMFLExtension(requestedMFLength);
+            } else {
+                requestedMFLength = -1;
+            }
+        }
+
         // reset the client random cookie
         clnt_random = clientHelloMessage.clnt_random;
 
         /*
          * need to set the renegotiation_info extension for:

@@ -1401,10 +1501,15 @@
         if (secureRenegotiation ||
                 !cipherSuites.contains(CipherSuite.C_SCSV)) {
             clientHelloMessage.addRenegotiationInfoExtension(clientVerifyData);
         }
 
+        if (isDTLS) {
+            // Cookie exchange need to reserve the initial ClientHello message.
+            initialClientHelloMsg = clientHelloMessage;
+        }
+
         return clientHelloMessage;
     }
 
     /*
      * Fault detected during handshake.