*/ sendChangeCipherAndFinish(false); } /* * "Finished" is the last handshake message sent. If we got this * far, the MAC has been validated post-decryption. We validate * the two hashes here as an additional sanity check, protecting * the handshake against various active attacks. */ private void serverFinished(Finished mesg) throws IOException { if (debug != null && Debug.isOn("handshake")) { mesg.print(System.out); } boolean verified = mesg.verify(handshakeHash, Finished.SERVER, session.getMasterSecret()); if (!verified) { fatalSE(Alerts.alert_illegal_parameter, "server 'finished' message doesn't verify"); // NOTREACHED } /* * save server verify data for secure renegotiation */ if (secureRenegotiation) { serverVerifyData = mesg.getVerifyData(); } /* * OK, it verified. If we're doing the fast handshake, add that * "Finished" message to the hash of handshake messages, then send * our own change_cipher_spec and Finished message for the server * to verify in turn. These are the last handshake messages. * * 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); } session.setLastAccessedTime(System.currentTimeMillis()); if (!resumingSession) { if (session.isRejoinable()) { ((SSLSessionContextImpl) sslContext .engineGetClientSessionContext()) .put(session); if (debug != null && Debug.isOn("session")) { System.out.println("%% Cached client session: " + session); } } else if (debug != null && Debug.isOn("session")) { System.out.println( "%% Didn't cache non-resumable client session: " + session); } } } /* * Send my change-cipher-spec and Finished message ... done as the * last handshake act in either the short or long sequences. In * 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 { Finished mesg = new Finished(protocolVersion, handshakeHash, Finished.CLIENT, session.getMasterSecret(), cipherSuite); /* * Send the change_cipher_spec message, then the Finished message * which we just calculated (and protected using the keys we just * calculated). Server responds with its Finished message, except * in the "fast handshake" (resume session) case. */ sendChangeCipherSpec(mesg, finishedTag); /* * 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 */ HandshakeMessage getKickstartMessage() throws SSLException { // session ID of the ClientHello message SessionId sessionId = SSLSessionImpl.nullSession.getSessionId(); // a list of cipher suites sent by the client CipherSuiteList cipherSuites = getActiveCipherSuites(); // set the max protocol version this client is supporting. maxProtocolVersion = protocolVersion; // // Try to resume an existing session. This might be mandatory, // given certain API options. // session = ((SSLSessionContextImpl)sslContext .engineGetClientSessionContext()) .get(getHostSE(), getPortSE()); if (debug != null && Debug.isOn("session")) { if (session != null) { System.out.println("%% Client cached " + session + (session.isRejoinable() ? "" : " (not rejoinable)")); } else { System.out.println("%% No cached client session"); } } if ((session != null) && (session.isRejoinable() == false)) { session = null; } if (session != null) { CipherSuite sessionSuite = session.getSuite(); ProtocolVersion sessionVersion = session.getProtocolVersion(); if (isNegotiable(sessionSuite) == false) { if (debug != null && Debug.isOn("session")) { System.out.println("%% can't resume, unavailable cipher"); } session = null; } if ((session != null) && !isNegotiable(sessionVersion)) { if (debug != null && Debug.isOn("session")) { System.out.println("%% can't resume, protocol disabled"); } session = null; } if (session != null) { if (debug != null) { if (Debug.isOn("handshake") || Debug.isOn("session")) { System.out.println("%% Try resuming " + session + " from port " + getLocalPortSE()); } } sessionId = session.getSessionId(); maxProtocolVersion = sessionVersion; // Update SSL version number in underlying SSL socket and // handshake output stream, so that the output records (at the // record layer) have the correct version setVersion(sessionVersion); } /* * Force use of the previous session ciphersuite, and * add the SCSV if enabled. */ if (!enableNewSession) { if (session == null) { throw new SSLHandshakeException( "Can't reuse existing SSL client session"); } Collection<CipherSuite> cipherList = new ArrayList<>(2); cipherList.add(sessionSuite); if (!secureRenegotiation && cipherSuites.contains(CipherSuite.C_SCSV)) { cipherList.add(CipherSuite.C_SCSV); } // otherwise, renegotiation_info extension will be used cipherSuites = new CipherSuiteList(cipherList); } } if (session == null && !enableNewSession) { throw new SSLHandshakeException("No existing session to resume"); } // exclude SCSV for secure renegotiation if (secureRenegotiation && cipherSuites.contains(CipherSuite.C_SCSV)) { Collection<CipherSuite> cipherList = new ArrayList<>(cipherSuites.size() - 1); for (CipherSuite suite : cipherSuites.collection()) { if (suite != CipherSuite.C_SCSV) { cipherList.add(suite); } } cipherSuites = new CipherSuiteList(cipherList); } // make sure there is a negotiable cipher suite. boolean negotiable = false; for (CipherSuite suite : cipherSuites.collection()) { if (isNegotiable(suite)) { negotiable = true; break; } } if (!negotiable) { throw new SSLHandshakeException("No negotiable cipher suite"); } // Not a TLS1.2+ handshake // For SSLv2Hello, HandshakeHash.reset() will be called, so we // cannot call HandshakeHash.protocolDetermined() here. As it does // not follow the spec that HandshakeHash.reset() can be only be // called before protocolDetermined. // if (maxProtocolVersion.v < ProtocolVersion.TLS12.v) { // handshakeHash.protocolDetermined(maxProtocolVersion); // } // create the ClientHello message ClientHello clientHelloMessage = new ClientHello( sslContext.getSecureRandom(), maxProtocolVersion, sessionId, cipherSuites); // add signature_algorithm extension if (maxProtocolVersion.v >= ProtocolVersion.TLS12.v) { // we will always send the signature_algorithm extension Collection<SignatureAndHashAlgorithm> localSignAlgs = getLocalSupportedSignAlgs(); if (localSignAlgs.isEmpty()) { throw new SSLHandshakeException( "No supported signature algorithm"); } clientHelloMessage.addSignatureAlgorithmsExtension(localSignAlgs); } // add server_name extension if (enableSNIExtension) { // We cannot use the hostname resolved from name services. For // virtual hosting, multiple hostnames may be bound to the same IP // address, so the hostname resolved from name services is not // reliable. String hostname = getRawHostnameSE(); // we only allow FQDN if (hostname != null && hostname.indexOf('.') > 0 && !IPAddressUtil.isIPv4LiteralAddress(hostname) && !IPAddressUtil.isIPv6LiteralAddress(hostname)) { clientHelloMessage.addServerNameIndicationExtension(hostname); } } // reset the client random cookie clnt_random = clientHelloMessage.clnt_random; /* * need to set the renegotiation_info extension for: * 1: secure renegotiation * 2: initial handshake and no SCSV in the ClientHello * 3: insecure renegotiation and no SCSV in the ClientHello */ if (secureRenegotiation || !cipherSuites.contains(CipherSuite.C_SCSV)) { clientHelloMessage.addRenegotiationInfoExtension(clientVerifyData); } return clientHelloMessage; } /* * Fault detected during handshake. */ void handshakeAlert(byte description) throws SSLProtocolException { String message = Alerts.alertDescription(description); if (debug != null && Debug.isOn("handshake")) { System.out.println("SSL - handshake alert: " + message); } throw new SSLProtocolException("handshake alert: " + message); } /* * Unless we are using an anonymous ciphersuite, the server always * sends a certificate message (for the CipherSuites we currently * support). The trust manager verifies the chain for us. */ private void serverCertificate(CertificateMsg mesg) throws IOException { if (debug != null && Debug.isOn("handshake")) { mesg.print(System.out); } X509Certificate[] peerCerts = mesg.getCertificateChain(); if (peerCerts.length == 0) { fatalSE(Alerts.alert_bad_certificate, "empty certificate chain"); } // ask the trust manager to verify the chain X509TrustManager tm = sslContext.getX509TrustManager(); try { // find out the key exchange algorithm used // use "RSA" for non-ephemeral "RSA_EXPORT" String keyExchangeString; if (keyExchange == K_RSA_EXPORT && !serverKeyExchangeReceived) { keyExchangeString = K_RSA.name; } else { keyExchangeString = keyExchange.name; } if (tm instanceof X509ExtendedTrustManager) { if (conn != null) { ((X509ExtendedTrustManager)tm).checkServerTrusted( peerCerts.clone(), keyExchangeString, conn); } else { ((X509ExtendedTrustManager)tm).checkServerTrusted( peerCerts.clone(), keyExchangeString, engine); } } else { // Unlikely to happen, because we have wrapped the old // X509TrustManager with the new X509ExtendedTrustManager. throw new CertificateException( "Improper X509TrustManager implementation"); } } catch (CertificateException e) { // This will throw an exception, so include the original error. fatalSE(Alerts.alert_certificate_unknown, e); } session.setPeerCertificates(peerCerts); }}Lines 1001–1341 of 1341. Previous lines
*/ sendChangeCipherAndFinish(false); } /* * "Finished" is the last handshake message sent. If we got this * far, the MAC has been validated post-decryption. We validate * the two hashes here as an additional sanity check, protecting * the handshake against various active attacks. */ private void serverFinished(Finished mesg) throws IOException { if (debug != null && Debug.isOn("handshake")) { mesg.print(System.out); } boolean verified = mesg.verify(handshakeHash, Finished.SERVER, session.getMasterSecret()); if (!verified) { fatalSE(Alerts.alert_illegal_parameter, "server 'finished' message doesn't verify"); // NOTREACHED } /* * save server verify data for secure renegotiation */ if (secureRenegotiation) { serverVerifyData = mesg.getVerifyData(); } /* * OK, it verified. If we're doing the fast handshake, add that * "Finished" message to the hash of handshake messages, then send * our own change_cipher_spec and Finished message for the server * to verify in turn. These are the last handshake messages. * * 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); } session.setLastAccessedTime(System.currentTimeMillis()); if (!resumingSession) { if (session.isRejoinable()) { ((SSLSessionContextImpl) sslContext .engineGetClientSessionContext()) .put(session); if (debug != null && Debug.isOn("session")) { System.out.println("%% Cached client session: " + session); } } else if (debug != null && Debug.isOn("session")) { System.out.println( "%% Didn't cache non-resumable client session: " + session); } } } /* * Send my change-cipher-spec and Finished message ... done as the * last handshake act in either the short or long sequences. In * 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 { Finished mesg = new Finished(protocolVersion, handshakeHash, Finished.CLIENT, session.getMasterSecret(), cipherSuite); /* * Send the change_cipher_spec message, then the Finished message * which we just calculated (and protected using the keys we just * calculated). Server responds with its Finished message, except * in the "fast handshake" (resume session) case. */ sendChangeCipherSpec(mesg, finishedTag); /* * 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 */ HandshakeMessage getKickstartMessage() throws SSLException { // session ID of the ClientHello message SessionId sessionId = SSLSessionImpl.nullSession.getSessionId(); // a list of cipher suites sent by the client CipherSuiteList cipherSuites = getActiveCipherSuites(); // set the max protocol version this client is supporting. maxProtocolVersion = protocolVersion; // // Try to resume an existing session. This might be mandatory, // given certain API options. // session = ((SSLSessionContextImpl)sslContext .engineGetClientSessionContext()) .get(getHostSE(), getPortSE()); if (debug != null && Debug.isOn("session")) { if (session != null) { System.out.println("%% Client cached " + session + (session.isRejoinable() ? "" : " (not rejoinable)")); } else { System.out.println("%% No cached client session"); } } if ((session != null) && (session.isRejoinable() == false)) { session = null; } if (session != null) { CipherSuite sessionSuite = session.getSuite(); ProtocolVersion sessionVersion = session.getProtocolVersion(); if (isNegotiable(sessionSuite) == false) { if (debug != null && Debug.isOn("session")) { System.out.println("%% can't resume, unavailable cipher"); } session = null; } if ((session != null) && !isNegotiable(sessionVersion)) { if (debug != null && Debug.isOn("session")) { System.out.println("%% can't resume, protocol disabled"); } session = null; } if (session != null) { if (debug != null) { if (Debug.isOn("handshake") || Debug.isOn("session")) { System.out.println("%% Try resuming " + session + " from port " + getLocalPortSE()); } } sessionId = session.getSessionId(); maxProtocolVersion = sessionVersion; // Update SSL version number in underlying SSL socket and // handshake output stream, so that the output records (at the // record layer) have the correct version setVersion(sessionVersion); } /* * Force use of the previous session ciphersuite, and * add the SCSV if enabled. */ if (!enableNewSession) { if (session == null) { throw new SSLHandshakeException( "Can't reuse existing SSL client session"); } Collection<CipherSuite> cipherList = new ArrayList<>(2); cipherList.add(sessionSuite); if (!secureRenegotiation && cipherSuites.contains(CipherSuite.C_SCSV)) { cipherList.add(CipherSuite.C_SCSV); } // otherwise, renegotiation_info extension will be used cipherSuites = new CipherSuiteList(cipherList); } } if (session == null && !enableNewSession) { throw new SSLHandshakeException("No existing session to resume"); } // exclude SCSV for secure renegotiation if (secureRenegotiation && cipherSuites.contains(CipherSuite.C_SCSV)) { Collection<CipherSuite> cipherList = new ArrayList<>(cipherSuites.size() - 1); for (CipherSuite suite : cipherSuites.collection()) { if (suite != CipherSuite.C_SCSV) { cipherList.add(suite); } } cipherSuites = new CipherSuiteList(cipherList); } // make sure there is a negotiable cipher suite. boolean negotiable = false; for (CipherSuite suite : cipherSuites.collection()) { if (isNegotiable(suite)) { negotiable = true; break; } } if (!negotiable) { throw new SSLHandshakeException("No negotiable cipher suite"); } // Not a TLS1.2+ handshake // For SSLv2Hello, HandshakeHash.reset() will be called, so we // cannot call HandshakeHash.protocolDetermined() here. As it does // not follow the spec that HandshakeHash.reset() can be only be // called before protocolDetermined. // if (maxProtocolVersion.v < ProtocolVersion.TLS12.v) { // handshakeHash.protocolDetermined(maxProtocolVersion); // } // create the ClientHello message ClientHello clientHelloMessage = new ClientHello( sslContext.getSecureRandom(), maxProtocolVersion, sessionId, cipherSuites); // add signature_algorithm extension if (maxProtocolVersion.v >= ProtocolVersion.TLS12.v) { // we will always send the signature_algorithm extension Collection<SignatureAndHashAlgorithm> localSignAlgs = getLocalSupportedSignAlgs(); if (localSignAlgs.isEmpty()) { throw new SSLHandshakeException( "No supported signature algorithm"); } clientHelloMessage.addSignatureAlgorithmsExtension(localSignAlgs); } // add server_name extension if (enableSNIExtension) { // We cannot use the hostname resolved from name services. For // virtual hosting, multiple hostnames may be bound to the same IP // address, so the hostname resolved from name services is not // reliable. String hostname = getRawHostnameSE(); // we only allow FQDN if (hostname != null && hostname.indexOf('.') > 0 && !IPAddressUtil.isIPv4LiteralAddress(hostname) && !IPAddressUtil.isIPv6LiteralAddress(hostname)) { clientHelloMessage.addServerNameIndicationExtension(hostname); } } // reset the client random cookie clnt_random = clientHelloMessage.clnt_random; /* * need to set the renegotiation_info extension for: * 1: secure renegotiation * 2: initial handshake and no SCSV in the ClientHello * 3: insecure renegotiation and no SCSV in the ClientHello */ if (secureRenegotiation || !cipherSuites.contains(CipherSuite.C_SCSV)) { clientHelloMessage.addRenegotiationInfoExtension(clientVerifyData); } return clientHelloMessage; } /* * Fault detected during handshake. */ void handshakeAlert(byte description) throws SSLProtocolException { String message = Alerts.alertDescription(description); if (debug != null && Debug.isOn("handshake")) { System.out.println("SSL - handshake alert: " + message); } throw new SSLProtocolException("handshake alert: " + message); } /* * Unless we are using an anonymous ciphersuite, the server always * sends a certificate message (for the CipherSuites we currently * support). The trust manager verifies the chain for us. */ private void serverCertificate(CertificateMsg mesg) throws IOException { if (debug != null && Debug.isOn("handshake")) { mesg.print(System.out); } X509Certificate[] peerCerts = mesg.getCertificateChain(); if (peerCerts.length == 0) { fatalSE(Alerts.alert_bad_certificate, "empty certificate chain"); } // ask the trust manager to verify the chain X509TrustManager tm = sslContext.getX509TrustManager(); try { // find out the key exchange algorithm used // use "RSA" for non-ephemeral "RSA_EXPORT" String keyExchangeString; if (keyExchange == K_RSA_EXPORT && !serverKeyExchangeReceived) { keyExchangeString = K_RSA.name; } else { keyExchangeString = keyExchange.name; } if (tm instanceof X509ExtendedTrustManager) { if (conn != null) { ((X509ExtendedTrustManager)tm).checkServerTrusted( peerCerts.clone(), keyExchangeString, conn); } else { ((X509ExtendedTrustManager)tm).checkServerTrusted( peerCerts.clone(), keyExchangeString, engine); } } else { // Unlikely to happen, because we have wrapped the old // X509TrustManager with the new X509ExtendedTrustManager. throw new CertificateException( "Improper X509TrustManager implementation"); } } catch (CertificateException e) { // This will throw an exception, so include the original error. fatalSE(Alerts.alert_certificate_unknown, e); } session.setPeerCertificates(peerCerts); } }