jdk/src/share/classes/sun/security/ssl/Handshaker.java
changeset 7039 6464c8e62a18
parent 6856 533f4ad71f88
child 7043 5e2d1edeb2c7
--- a/jdk/src/share/classes/sun/security/ssl/Handshaker.java	Fri Oct 29 12:35:07 2010 +0200
+++ b/jdk/src/share/classes/sun/security/ssl/Handshaker.java	Sat Oct 30 18:39:17 2010 +0800
@@ -71,11 +71,31 @@
     byte[]              clientVerifyData;
     byte[]              serverVerifyData;
 
-    // is it an initial negotiation  or a renegotiation?
+    // Is it an initial negotiation  or a renegotiation?
     boolean                     isInitialHandshake;
 
-    // list of enabled protocols
-    ProtocolList enabledProtocols;
+    // List of enabled protocols
+    private ProtocolList        enabledProtocols;
+
+    // List of enabled CipherSuites
+    private CipherSuiteList     enabledCipherSuites;
+
+    /*
+     * List of active protocols
+     *
+     * Active protocols is a subset of enabled protocols, and will
+     * contain only those protocols that have vaild cipher suites
+     * enabled.
+     */
+    private ProtocolList       activeProtocols;
+
+    /*
+     * List of active cipher suites
+     *
+     * Active cipher suites is a subset of enabled cipher suites, and will
+     * contain only those cipher suites available for the active protocols.
+     */
+    private CipherSuiteList    activeCipherSuites;
 
     private boolean             isClient;
 
@@ -94,9 +114,6 @@
     // in reset state after use.
     private MessageDigest md5Tmp, shaTmp;
 
-    // list of enabled CipherSuites
-    CipherSuiteList     enabledCipherSuites;
-
     // current CipherSuite. Never null, initially SSL_NULL_WITH_NULL_NULL
     CipherSuite         cipherSuite;
 
@@ -233,7 +250,7 @@
         // client's cert verify, those constants are in a convenient
         // order to drastically simplify state machine checking.
         //
-        state = -1;
+        state = -2;  // initialized but not activated
     }
 
     /*
@@ -345,26 +362,69 @@
     void setVersion(ProtocolVersion protocolVersion) {
         this.protocolVersion = protocolVersion;
         setVersionSE(protocolVersion);
+
         output.r.setVersion(protocolVersion);
     }
 
 
     /**
      * Set the enabled protocols. Called from the constructor or
-     * SSLSocketImpl.setEnabledProtocols() (if the handshake is not yet
-     * in progress).
+     * SSLSocketImpl/SSLEngineImpl.setEnabledProtocols() (if the
+     * handshake is not yet in progress).
      */
     void setEnabledProtocols(ProtocolList enabledProtocols) {
+        activeCipherSuites = null;
+        activeProtocols = null;
+
         this.enabledProtocols = enabledProtocols;
+    }
+
+    /**
+     * Set the enabled cipher suites. Called from
+     * SSLSocketImpl/SSLEngineImpl.setEnabledCipherSuites() (if the
+     * handshake is not yet in progress).
+     */
+    void setEnabledCipherSuites(CipherSuiteList enabledCipherSuites) {
+        activeCipherSuites = null;
+        activeProtocols = null;
+        this.enabledCipherSuites = enabledCipherSuites;
+    }
+
+
+    /**
+     * Prior to handshaking, activate the handshake and initialize the version,
+     * input stream and output stream.
+     */
+    void activate(ProtocolVersion helloVersion) throws IOException {
+        if (activeProtocols == null) {
+            activeProtocols = getActiveProtocols();
+        }
+
+        if (activeProtocols.collection().isEmpty() ||
+                activeProtocols.max.v == ProtocolVersion.NONE.v) {
+            throw new SSLHandshakeException("No appropriate protocol");
+        }
+
+        if (activeCipherSuites == null) {
+            activeCipherSuites = getActiveCipherSuites();
+        }
+
+        if (activeCipherSuites.collection().isEmpty()) {
+            throw new SSLHandshakeException("No appropriate cipher suite");
+        }
 
         // temporary protocol version until the actual protocol version
         // is negotiated in the Hello exchange. This affects the record
-        // version we sent with the ClientHello. Using max() as the record
-        // version is not really correct but some implementations fail to
-        // correctly negotiate TLS otherwise.
-        protocolVersion = enabledProtocols.max;
+        // version we sent with the ClientHello.
+        if (!isInitialHandshake) {
+            protocolVersion = activeProtocolVersion;
+        } else {
+            protocolVersion = activeProtocols.max;
+        }
 
-        ProtocolVersion helloVersion = enabledProtocols.helloVersion;
+        if (helloVersion == null || helloVersion.v == ProtocolVersion.NONE.v) {
+            helloVersion = activeProtocols.helloVersion;
+        }
 
         input = new HandshakeInStream(handshakeHash);
 
@@ -372,12 +432,16 @@
             output = new HandshakeOutStream(protocolVersion, helloVersion,
                                         handshakeHash, conn);
             conn.getAppInputStream().r.setHelloVersion(helloVersion);
+            conn.getAppOutputStream().r.setHelloVersion(helloVersion);
         } else {
             output = new HandshakeOutStream(protocolVersion, helloVersion,
                                         handshakeHash, engine);
+            engine.inputRecord.setHelloVersion(helloVersion);
             engine.outputRecord.setHelloVersion(helloVersion);
         }
 
+        // move state to activated
+        state = -1;
     }
 
     /**
@@ -392,20 +456,127 @@
 
     /**
      * Check if the given ciphersuite is enabled and available.
-     * (Enabled ciphersuites are always available unless the status has
-     * changed due to change in JCE providers since it was enabled).
      * Does not check if the required server certificates are available.
      */
     boolean isNegotiable(CipherSuite s) {
-        return enabledCipherSuites.contains(s) && s.isNegotiable();
+        if (activeCipherSuites == null) {
+            activeCipherSuites = getActiveCipherSuites();
+        }
+
+        return activeCipherSuites.contains(s) && s.isNegotiable();
+    }
+
+    /**
+     * Check if the given protocol version is enabled and available.
+     */
+    boolean isNegotiable(ProtocolVersion protocolVersion) {
+        if (activeProtocols == null) {
+            activeProtocols = getActiveProtocols();
+        }
+
+        return activeProtocols.contains(protocolVersion);
+    }
+
+    /**
+     * Select a protocol version from the list. Called from
+     * ServerHandshaker to negotiate protocol version.
+     *
+     * Return the lower of the protocol version suggested in the
+     * clien hello and the highest supported by the server.
+     */
+    ProtocolVersion selectProtocolVersion(ProtocolVersion protocolVersion) {
+        if (activeProtocols == null) {
+            activeProtocols = getActiveProtocols();
+        }
+
+        return activeProtocols.selectProtocolVersion(protocolVersion);
     }
 
     /**
-     * As long as handshaking has not started, we can
+     * Get the active cipher suites.
+     *
+     * In TLS 1.1, many weak or vulnerable cipher suites were obsoleted,
+     * such as TLS_RSA_EXPORT_WITH_RC4_40_MD5. The implementation MUST NOT
+     * negotiate these cipher suites in TLS 1.1 or later mode.
+     *
+     * Therefore, when the active protocols only include TLS 1.1 or later,
+     * the client cannot request to negotiate those obsoleted cipher
+     * suites, that's, the obsoleted suites should not be included in the
+     * client hello. So we need to create a subset of the enabled cipher
+     * suites, the active cipher suites, which does not contain obsoleted
+     * cipher suites of the minimum active protocol.
+     *
+     * Return empty list instead of null if no active cipher suites.
+     */
+    CipherSuiteList getActiveCipherSuites() {
+        if (activeCipherSuites == null) {
+            if (activeProtocols == null) {
+                activeProtocols = getActiveProtocols();
+            }
+
+            ArrayList<CipherSuite> suites = new ArrayList<CipherSuite>();
+            if (!(activeProtocols.collection().isEmpty()) &&
+                    activeProtocols.min.v != ProtocolVersion.NONE.v) {
+                for (CipherSuite suite : enabledCipherSuites.collection()) {
+                    if (suite.obsoleted > activeProtocols.min.v) {
+                        suites.add(suite);
+                    } else if (debug != null && Debug.isOn("handshake")) {
+                        System.out.println(
+                            "Ignoring obsoleted cipher suite: " + suite);
+                    }
+                }
+            }
+            activeCipherSuites = new CipherSuiteList(suites);
+        }
+
+        return activeCipherSuites;
+    }
+
+    /*
+     * Get the active protocol versions.
+     *
+     * In TLS 1.1, many weak or vulnerable cipher suites were obsoleted,
+     * such as TLS_RSA_EXPORT_WITH_RC4_40_MD5. The implementation MUST NOT
+     * negotiate these cipher suites in TLS 1.1 or later mode.
+     *
+     * For example, if "TLS_RSA_EXPORT_WITH_RC4_40_MD5" is the
+     * only enabled cipher suite, the client cannot request TLS 1.1 or
+     * later, even though TLS 1.1 or later is enabled.  We need to create a
+     * subset of the enabled protocols, called the active protocols, which
+     * contains protocols appropriate to the list of enabled Ciphersuites.
+     *
+     * Return empty list instead of null if no active protocol versions.
+     */
+    ProtocolList getActiveProtocols() {
+        if (activeProtocols == null) {
+            ArrayList<ProtocolVersion> protocols =
+                                            new ArrayList<ProtocolVersion>(3);
+            for (ProtocolVersion protocol : enabledProtocols.collection()) {
+                boolean found = false;
+                for (CipherSuite suite : enabledCipherSuites.collection()) {
+                    if (suite.isAvailable() && suite.obsoleted > protocol.v) {
+                        protocols.add(protocol);
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found && (debug != null) && Debug.isOn("handshake")) {
+                    System.out.println(
+                        "No available cipher suite for " + protocol);
+                }
+            }
+            activeProtocols = new ProtocolList(protocols);
+        }
+
+        return activeProtocols;
+    }
+
+    /**
+     * As long as handshaking has not activated, we can
      * change whether session creations are allowed.
      *
      * Callers should do their own checking if handshaking
-     * has started.
+     * has activated.
      */
     void setEnableSessionCreation(boolean newSessions) {
         enableNewSession = newSessions;
@@ -419,12 +590,12 @@
         CipherBox box;
         if (isClient) {
             box = cipher.newCipher(protocolVersion, svrWriteKey, svrWriteIV,
-                                   false);
+                                   sslContext.getSecureRandom(), false);
             svrWriteKey = null;
             svrWriteIV = null;
         } else {
             box = cipher.newCipher(protocolVersion, clntWriteKey, clntWriteIV,
-                                   false);
+                                   sslContext.getSecureRandom(), false);
             clntWriteKey = null;
             clntWriteIV = null;
         }
@@ -439,12 +610,12 @@
         CipherBox box;
         if (isClient) {
             box = cipher.newCipher(protocolVersion, clntWriteKey, clntWriteIV,
-                                   true);
+                                   sslContext.getSecureRandom(), true);
             clntWriteKey = null;
             clntWriteIV = null;
         } else {
             box = cipher.newCipher(protocolVersion, svrWriteKey, svrWriteIV,
-                                   true);
+                                   sslContext.getSecureRandom(), true);
             svrWriteKey = null;
             svrWriteIV = null;
         }
@@ -614,13 +785,20 @@
 
 
     /**
+     * Returns true iff the handshaker has been activated.
+     *
+     * In activated state, the handshaker may not send any messages out.
+     */
+    boolean activated() {
+        return state >= -1;
+    }
+
+    /**
      * Returns true iff the handshaker has sent any messages.
-     * Server kickstarting is not as neat as it should be; we
-     * need to create a new handshaker, this method lets us
-     * know if we should.
      */
     boolean started() {
-        return state >= 0;
+        return state >= 0;  // 0: HandshakeMessage.ht_hello_request
+                            // 1: HandshakeMessage.ht_hello_request
     }
 
 
@@ -633,6 +811,7 @@
         if (state >= 0) {
             return;
         }
+
         HandshakeMessage m = getKickstartMessage();
 
         if (debug != null && Debug.isOn("handshake")) {
@@ -746,6 +925,7 @@
      */
     private SecretKey calculateMasterSecret(SecretKey preMasterSecret,
             ProtocolVersion requestedVersion) {
+
         TlsMasterSecretParameterSpec spec = new TlsMasterSecretParameterSpec
                 (preMasterSecret, protocolVersion.major, protocolVersion.minor,
                 clnt_random.random_bytes, svr_random.random_bytes);
@@ -773,22 +953,37 @@
             if (!preMasterSecret.getAlgorithm().equals("TlsRsaPremasterSecret")) {
                 throw new ProviderException(e);
             }
+
             if (debug != null && Debug.isOn("handshake")) {
                 System.out.println("RSA master secret generation error:");
                 e.printStackTrace(System.out);
                 System.out.println("Generating new random premaster secret");
             }
-            preMasterSecret = RSAClientKeyExchange.generateDummySecret(protocolVersion);
+
+            if (requestedVersion != null) {
+                preMasterSecret =
+                    RSAClientKeyExchange.generateDummySecret(requestedVersion);
+            } else {
+                preMasterSecret =
+                    RSAClientKeyExchange.generateDummySecret(protocolVersion);
+            }
+
             // recursive call with new premaster secret
             return calculateMasterSecret(preMasterSecret, null);
         }
 
-        // if no version check requested (client side handshake),
-        // or version information is not available (not an RSA premaster secret),
+        // if no version check requested (client side handshake), or version
+        // information is not available (not an RSA premaster secret),
         // return master secret immediately.
-        if ((requestedVersion == null) || !(masterSecret instanceof TlsMasterSecret)) {
+        if ((requestedVersion == null) ||
+                !(masterSecret instanceof TlsMasterSecret)) {
             return masterSecret;
         }
+
+        // we have checked the ClientKeyExchange message when reading TLS
+        // record, the following check is necessary to ensure that
+        // JCE provider does not ignore the checking, or the previous
+        // checking process bypassed the premaster secret version checking.
         TlsMasterSecret tlsKey = (TlsMasterSecret)masterSecret;
         int major = tlsKey.getMajorVersion();
         int minor = tlsKey.getMinorVersion();
@@ -800,13 +995,21 @@
         // the specification says that it must be the maximum version supported
         // by the client from its ClientHello message. However, many
         // implementations send the negotiated version, so accept both
-        // NOTE that we may be comparing two unsupported version numbers in
-        // the second case, which is why we cannot use object reference
-        // equality in this special case
-        ProtocolVersion premasterVersion = ProtocolVersion.valueOf(major, minor);
-        boolean versionMismatch = (premasterVersion != protocolVersion) &&
-                                  (premasterVersion.v != requestedVersion.v);
+        // for SSL v3.0 and TLS v1.0.
+        // NOTE that we may be comparing two unsupported version numbers, which
+        // is why we cannot use object reference equality in this special case.
+        ProtocolVersion premasterVersion =
+                                    ProtocolVersion.valueOf(major, minor);
+        boolean versionMismatch = (premasterVersion.v != requestedVersion.v);
 
+        /*
+         * we never checked the client_version in server side
+         * for TLS v1.0 and SSL v3.0. For compatibility, we
+         * maintain this behavior.
+         */
+        if (versionMismatch && requestedVersion.v <= ProtocolVersion.TLS10.v) {
+            versionMismatch = (premasterVersion.v != protocolVersion.v);
+        }
 
         if (versionMismatch == false) {
             // check passed, return key
@@ -823,7 +1026,9 @@
                 + premasterVersion);
             System.out.println("Generating new random premaster secret");
         }
-        preMasterSecret = RSAClientKeyExchange.generateDummySecret(protocolVersion);
+        preMasterSecret =
+            RSAClientKeyExchange.generateDummySecret(requestedVersion);
+
         // recursive call with new premaster secret
         return calculateMasterSecret(preMasterSecret, null);
     }
@@ -849,8 +1054,6 @@
         int hashSize = cipherSuite.macAlg.size;
         boolean is_exportable = cipherSuite.exportable;
         BulkCipher cipher = cipherSuite.cipher;
-        int keySize = cipher.keySize;
-        int ivSize = cipher.ivSize;
         int expandedKeySize = is_exportable ? cipher.expandedKeySize : 0;
 
         TlsKeyMaterialParameterSpec spec = new TlsKeyMaterialParameterSpec
@@ -867,6 +1070,8 @@
             clntWriteKey = keySpec.getClientCipherKey();
             svrWriteKey = keySpec.getServerCipherKey();
 
+            // Return null if IVs are not supposed to be generated.
+            // e.g. TLS 1.1+.
             clntWriteIV = keySpec.getClientIv();
             svrWriteIV = keySpec.getServerIv();
 
@@ -914,7 +1119,12 @@
                     System.out.println("Server write IV:");
                     printHex(dump, svrWriteIV.getIV());
                 } else {
-                    System.out.println("... no IV used for this cipher");
+                    if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+                        System.out.println(
+                                "... no IV derived for this protocol");
+                    } else {
+                        System.out.println("... no IV used for this cipher");
+                    }
                 }
                 System.out.flush();
             }