--- old/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java 2018-05-11 15:08:56.018815800 -0700 +++ /dev/null 2018-05-11 10:42:23.849000000 -0700 @@ -1,2004 +0,0 @@ -/* - * Copyright (c) 1996, 2017, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package sun.security.ssl; - -import java.io.*; -import java.math.BigInteger; -import java.security.*; -import java.util.*; - -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.ECParameterSpec; - -import java.security.cert.X509Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateParsingException; -import java.security.cert.CertPathValidatorException; -import java.security.cert.CertPathValidatorException.Reason; -import java.security.cert.CertPathValidatorException.BasicReason; -import javax.security.auth.x500.X500Principal; - -import javax.crypto.SecretKey; - -import javax.net.ssl.*; - -import sun.security.ssl.HandshakeMessage.*; -import static sun.security.ssl.CipherSuite.KeyExchange.*; - -/** - * ClientHandshaker does the protocol handshaking from the point - * of view of a client. It is driven asychronously by handshake messages - * as delivered by the parent Handshaker class, and also uses - * common functionality (e.g. key generation) that is provided there. - * - * @author David Brownell - */ -final class ClientHandshaker extends Handshaker { - - // constants for subject alt names of type DNS and IP - private static final int ALTNAME_DNS = 2; - private static final int ALTNAME_IP = 7; - - // the server's public key from its certificate. - private PublicKey serverKey; - - // the server's ephemeral public key from the server key exchange message - // for ECDHE/ECDH_anon and RSA_EXPORT. - private PublicKey ephemeralServerKey; - - // server's ephemeral public value for DHE/DH_anon key exchanges - private BigInteger serverDH; - - private DHCrypt dh; - - private ECDHCrypt ecdh; - - private CertificateRequest certRequest; - - private boolean serverKeyExchangeReceived; - - private boolean staplingActive = false; - private X509Certificate[] deferredCerts; - - /* - * The RSA PreMasterSecret needs to know the version of - * ClientHello that was used on this handshake. This represents - * the "max version" this client is supporting. In the - * case of an initial handshake, it's the max version enabled, - * but in the case of a resumption attempt, it's the version - * of the session we're trying to resume. - */ - private ProtocolVersion maxProtocolVersion; - - // To switch off the SNI extension. - private static final boolean enableSNIExtension = - Debug.getBooleanProperty("jsse.enableSNIExtension", true); - - /* - * Allow unsafe server certificate change? - * - * Server certificate change during SSL/TLS renegotiation may be considered - * unsafe, as described in the Triple Handshake attacks: - * - * https://secure-resumption.com/tlsauth.pdf - * - * Endpoint identification (See - * SSLParameters.getEndpointIdentificationAlgorithm()) is a pretty nice - * guarantee that the server certificate change in renegotiation is legal. - * However, endpoing identification is only enabled for HTTPS and LDAP - * over SSL/TLS by default. It is not enough to protect SSL/TLS - * connections other than HTTPS and LDAP. - * - * The renegotiation indication extension (See RFC 5764) is a pretty - * strong guarantee that the endpoints on both client and server sides - * are identical on the same connection. However, the Triple Handshake - * attacks can bypass this guarantee if there is a session-resumption - * handshake between the initial full handshake and the renegotiation - * full handshake. - * - * Server certificate change may be unsafe and should be restricted if - * endpoint identification is not enabled and the previous handshake is - * a session-resumption abbreviated initial handshake, unless the - * identities represented by both certificates can be regraded as the - * same (See isIdentityEquivalent()). - * - * Considering the compatibility impact and the actual requirements to - * support server certificate change in practice, the system property, - * jdk.tls.allowUnsafeServerCertChange, is used to define whether unsafe - * server certificate change in renegotiation is allowed or not. The - * default value of the system property is "false". To mitigate the - * compactibility impact, applications may want to set the system - * property to "true" at their own risk. - * - * If the value of the system property is "false", server certificate - * change in renegotiation after a session-resumption abbreviated initial - * handshake is restricted (See isIdentityEquivalent()). - * - * If the system property is set to "true" explicitly, the restriction on - * server certificate change in renegotiation is disabled. - */ - private static final boolean allowUnsafeServerCertChange = - Debug.getBooleanProperty("jdk.tls.allowUnsafeServerCertChange", false); - - // To switch off the max_fragment_length extension. - private static final boolean enableMFLExtension = - Debug.getBooleanProperty("jsse.enableMFLExtension", false); - - // To switch off the supported_groups extension for DHE cipher suite. - private static final boolean enableFFDHE = - Debug.getBooleanProperty("jsse.enableFFDHE", true); - - // Whether an ALPN extension was sent in the ClientHello - private boolean alpnActive = false; - - private List requestedServerNames = - Collections.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. - */ - private X509Certificate[] reservedServerCerts = null; - - /* - * Constructors - */ - ClientHandshaker(SSLSocketImpl socket, SSLContextImpl context, - ProtocolList enabledProtocols, - ProtocolVersion activeProtocolVersion, - boolean isInitialHandshake, boolean secureRenegotiation, - byte[] clientVerifyData, byte[] serverVerifyData) { - - super(socket, context, enabledProtocols, true, true, - activeProtocolVersion, isInitialHandshake, secureRenegotiation, - clientVerifyData, serverVerifyData); - } - - ClientHandshaker(SSLEngineImpl engine, SSLContextImpl context, - ProtocolList enabledProtocols, - ProtocolVersion activeProtocolVersion, - boolean isInitialHandshake, boolean secureRenegotiation, - byte[] clientVerifyData, byte[] serverVerifyData, - boolean isDTLS) { - - super(engine, context, enabledProtocols, true, true, - activeProtocolVersion, isInitialHandshake, secureRenegotiation, - 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 - * spec) it parses the type-specific message. Then it calls a function - * that handles that specific message. - * - * It updates the state machine (need to verify it) as each message - * is processed, and writes responses as needed using the connection - * in the constructor. - */ - @Override - void processMessage(byte type, int messageLen) throws IOException { - // check the handshake state - List ignoredOptStates = handshakeState.check(type); - - // If the state machine has skipped over certificate status - // and stapling was enabled, we need to check the chain immediately - // because it was deferred, waiting for CertificateStatus. - if (staplingActive && ignoredOptStates.contains( - HandshakeMessage.ht_certificate_status)) { - checkServerCerts(deferredCerts); - serverKey = session.getPeerCertificates()[0].getPublicKey(); - } - - switch (type) { - case HandshakeMessage.ht_hello_request: - 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: - 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 - || ClientKeyExchangeService.find(keyExchange.name) != null) { - // No external key exchange provider needs a cert now. - fatalSE(Alerts.alert_unexpected_message, - "unexpected server cert chain"); - // NOTREACHED - } - CertificateMsg certificateMsg = new CertificateMsg(input); - handshakeState.update(certificateMsg, resumingSession); - this.serverCertificate(certificateMsg); - if (!staplingActive) { - // If we are not doing stapling, we can set serverKey right - // away. Otherwise, we will wait until verification of the - // chain has completed after CertificateStatus; - serverKey = session.getPeerCertificates()[0].getPublicKey(); - } - break; - - case HandshakeMessage.ht_certificate_status: - CertificateStatus certStatusMsg = new CertificateStatus(input); - handshakeState.update(certStatusMsg, resumingSession); - this.certificateStatus(certStatusMsg); - serverKey = session.getPeerCertificates()[0].getPublicKey(); - break; - - case HandshakeMessage.ht_server_key_exchange: - serverKeyExchangeReceived = true; - switch (keyExchange) { - case K_RSA_EXPORT: - /** - * The server key exchange message is sent by the server only - * when the server certificate message does not contain the - * proper amount of data to allow the client to exchange a - * premaster secret, such as when RSA_EXPORT is used and the - * public key in the server certificate is longer than 512 bits. - */ - if (serverKey == null) { - throw new SSLProtocolException - ("Server did not send certificate message"); - } - - if (!(serverKey instanceof RSAPublicKey)) { - throw new SSLProtocolException("Protocol violation:" + - " the certificate type must be appropriate for the" + - " selected cipher suite's key exchange algorithm"); - } - - if (JsseJce.getRSAKeyLength(serverKey) <= 512) { - throw new SSLProtocolException("Protocol violation:" + - " server sent a server key exchange message for" + - " key exchange " + keyExchange + - " when the public key in the server certificate" + - " is less than or equal to 512 bits in length"); - } - - try { - RSA_ServerKeyExchange rsaSrvKeyExchange = - new RSA_ServerKeyExchange(input); - handshakeState.update(rsaSrvKeyExchange, resumingSession); - this.serverKeyExchange(rsaSrvKeyExchange); - } catch (GeneralSecurityException e) { - throw new SSLException("Server key", e); - } - break; - case K_DH_ANON: - try { - DH_ServerKeyExchange dhSrvKeyExchange = - new DH_ServerKeyExchange(input, protocolVersion); - handshakeState.update(dhSrvKeyExchange, resumingSession); - this.serverKeyExchange(dhSrvKeyExchange); - } catch (GeneralSecurityException e) { - throw new SSLException("Server key", e); - } - break; - case K_DHE_DSS: - case K_DHE_RSA: - try { - DH_ServerKeyExchange dhSrvKeyExchange = - new DH_ServerKeyExchange( - input, serverKey, - clnt_random.random_bytes, svr_random.random_bytes, - messageLen, - getLocalSupportedSignAlgs(), protocolVersion); - handshakeState.update(dhSrvKeyExchange, resumingSession); - this.serverKeyExchange(dhSrvKeyExchange); - } catch (GeneralSecurityException e) { - throw new SSLException("Server key", e); - } - break; - case K_ECDHE_ECDSA: - case K_ECDHE_RSA: - case K_ECDH_ANON: - try { - ECDH_ServerKeyExchange ecdhSrvKeyExchange = - new ECDH_ServerKeyExchange - (input, serverKey, clnt_random.random_bytes, - svr_random.random_bytes, - getLocalSupportedSignAlgs(), protocolVersion); - handshakeState.update(ecdhSrvKeyExchange, resumingSession); - this.serverKeyExchange(ecdhSrvKeyExchange); - } catch (GeneralSecurityException e) { - throw new SSLException("Server key", e); - } - break; - case K_RSA: - case K_DH_RSA: - case K_DH_DSS: - case K_ECDH_ECDSA: - case K_ECDH_RSA: - throw new SSLProtocolException( - "Protocol violation: server sent a server key exchange" - + " message for key exchange " + keyExchange); - default: - throw new SSLProtocolException( - "unsupported or unexpected key exchange algorithm = " - + keyExchange); - } - break; - - case HandshakeMessage.ht_certificate_request: - // save for later, it's handled by serverHelloDone - if ((keyExchange == K_DH_ANON) || (keyExchange == K_ECDH_ANON)) { - throw new SSLHandshakeException( - "Client authentication requested for "+ - "anonymous cipher suite."); - } else if (ClientKeyExchangeService.find(keyExchange.name) != null) { - // No external key exchange provider needs a cert now. - throw new SSLHandshakeException( - "Client certificate requested for "+ - "external cipher suite: " + keyExchange); - } - certRequest = new CertificateRequest(input, protocolVersion); - if (debug != null && Debug.isOn("handshake")) { - certRequest.print(System.out); - } - handshakeState.update(certRequest, resumingSession); - - if (protocolVersion.useTLS12PlusSpec()) { - Collection peerSignAlgs = - certRequest.getSignAlgorithms(); - if (peerSignAlgs == null || peerSignAlgs.isEmpty()) { - throw new SSLHandshakeException( - "No peer supported signature algorithms"); - } - - Collection supportedPeerSignAlgs = - SignatureAndHashAlgorithm.getSupportedAlgorithms( - algorithmConstraints, peerSignAlgs); - if (supportedPeerSignAlgs.isEmpty()) { - throw new SSLHandshakeException( - "No supported signature and hash algorithm in common"); - } - - setPeerSupportedSignAlgs(supportedPeerSignAlgs); - session.setPeerSupportedSignatureAlgorithms( - supportedPeerSignAlgs); - } - - break; - - case HandshakeMessage.ht_server_hello_done: - ServerHelloDone serverHelloDone = new ServerHelloDone(input); - handshakeState.update(serverHelloDone, resumingSession); - this.serverHelloDone(serverHelloDone); - - break; - - case HandshakeMessage.ht_finished: - Finished serverFinished = - new Finished(protocolVersion, input, cipherSuite); - handshakeState.update(serverFinished, resumingSession); - this.serverFinished(serverFinished); - - break; - - default: - throw new SSLProtocolException( - "Illegal client handshake msg, " + 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 - * the client to present a certificate). - */ - private void serverHelloRequest(HelloRequest mesg) throws IOException { - if (debug != null && Debug.isOn("handshake")) { - mesg.print(System.out); - } - - // - // Could be (e.g. at connection setup) that we already - // sent the "client hello" but the server's not seen it. - // - if (!clientHelloDelivered) { - if (!secureRenegotiation && !allowUnsafeRenegotiation) { - // renegotiation is not allowed. - 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. - invalidated = true; - - // If there is still unread block in the handshake - // input stream, it would be truncated with the disposal - // and the next handshake message will become incomplete. - // - // However, according to SSL/TLS specifications, no more - // handshake message should immediately follow ClientHello - // or HelloRequest. So just let it be. - } else { - // For SSLv3, send the handshake_failure fatal error. - // Note that SSLv3 does not define a no_renegotiation - // alert like TLSv1. However we cannot ignore the message - // simply, otherwise the other side was waiting for a - // response that would never come. - fatalSE(Alerts.alert_handshake_failure, - "Renegotiation is not allowed"); - } - } else { - if (!secureRenegotiation) { - if (debug != null && Debug.isOn("handshake")) { - System.out.println( - "Warning: continue with insecure renegotiation"); - } - } - 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. - * - * There are two branches of the state machine, decided by the - * details of this message. One is the "fast" handshake, where we - * 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); - } - - // check if the server selected protocol version is OK for us - ProtocolVersion mesgVersion = mesg.protocolVersion; - if (!isNegotiable(mesgVersion)) { - throw new SSLHandshakeException( - "Server chose " + mesgVersion + - ", but that protocol version is not enabled or not supported " + - "by the client."); - } - - handshakeHash.protocolDetermined(mesgVersion); - - // Set protocolVersion and propagate to SSLSocket and the - // Handshake streams - setVersion(mesgVersion); - - // check the "renegotiation_info" extension - RenegotiationInfoExtension serverHelloRI = (RenegotiationInfoExtension) - mesg.extensions.get(ExtensionType.EXT_RENEGOTIATION_INFO); - if (serverHelloRI != null) { - if (isInitialHandshake) { - // verify the length of the "renegotiated_connection" field - if (!serverHelloRI.isEmpty()) { - // abort the handshake with a fatal handshake_failure alert - fatalSE(Alerts.alert_handshake_failure, - "The renegotiation_info field is not empty"); - } - - secureRenegotiation = true; - } else { - // For a legacy renegotiation, the client MUST verify that - // it does not contain the "renegotiation_info" extension. - if (!secureRenegotiation) { - fatalSE(Alerts.alert_handshake_failure, - "Unexpected renegotiation indication extension"); - } - - // verify the client_verify_data and server_verify_data values - byte[] verifyData = - new byte[clientVerifyData.length + serverVerifyData.length]; - System.arraycopy(clientVerifyData, 0, verifyData, - 0, clientVerifyData.length); - System.arraycopy(serverVerifyData, 0, verifyData, - clientVerifyData.length, serverVerifyData.length); - if (!MessageDigest.isEqual(verifyData, - serverHelloRI.getRenegotiatedConnection())) { - fatalSE(Alerts.alert_handshake_failure, - "Incorrect verify data in ServerHello " + - "renegotiation_info message"); - } - } - } else { - // no renegotiation indication extension - if (isInitialHandshake) { - if (!allowLegacyHelloMessages) { - // abort the handshake with a fatal handshake_failure alert - fatalSE(Alerts.alert_handshake_failure, - "Failed to negotiate the use of secure renegotiation"); - } - - secureRenegotiation = false; - if (debug != null && Debug.isOn("handshake")) { - System.out.println("Warning: No renegotiation " + - "indication extension in ServerHello"); - } - } else { - // For a secure renegotiation, the client must abort the - // handshake if no "renegotiation_info" extension is present. - if (secureRenegotiation) { - fatalSE(Alerts.alert_handshake_failure, - "No renegotiation indication extension"); - } - - // we have already allowed unsafe renegotation before request - // the renegotiation. - } - } - - // - // Save server nonce, we always use it to compute connection - // keys and it's also used to create the master secret if we're - // creating a new session (i.e. in the full handshake). - // - svr_random = mesg.svr_random; - - if (isNegotiable(mesg.cipherSuite) == false) { - fatalSE(Alerts.alert_illegal_parameter, - "Server selected improper ciphersuite " + mesg.cipherSuite); - } - - setCipherSuite(mesg.cipherSuite); - if (protocolVersion.useTLS12PlusSpec()) { - handshakeHash.setFinishedAlg(cipherSuite.prfAlg.getPRFHashAlg()); - } - - if (mesg.compression_method != 0) { - fatalSE(Alerts.alert_illegal_parameter, - "compression type not supported, " - + mesg.compression_method); - // NOTREACHED - } - - // so far so good, let's look at the session - if (session != null) { - // we tried to resume, let's see what the server decided - if (session.getSessionId().equals(mesg.sessionId)) { - // server resumed the session, let's make sure everything - // checks out - - // Verify that the session ciphers are unchanged. - CipherSuite sessionSuite = session.getSuite(); - if (cipherSuite != sessionSuite) { - throw new SSLProtocolException - ("Server returned wrong cipher suite for session"); - } - - // verify protocol version match - ProtocolVersion sessionVersion = session.getProtocolVersion(); - if (protocolVersion != sessionVersion) { - throw new SSLProtocolException - ("Server resumed session with wrong protocol version"); - } - - // validate subject identity - ClientKeyExchangeService p = - ClientKeyExchangeService.find( - sessionSuite.keyExchange.name); - if (p != null) { - Principal localPrincipal = session.getLocalPrincipal(); - - if (p.isRelated(true, getAccSE(), localPrincipal)) { - if (debug != null && Debug.isOn("session")) - System.out.println("Subject identity is same"); - } else { - throw new SSLProtocolException( - "Server resumed session with " + - "wrong subject identity or no subject"); - } - } - - // looks fine; resume it. - resumingSession = true; - calculateConnectionKeys(session.getMasterSecret()); - if (debug != null && Debug.isOn("session")) { - System.out.println("%% Server resumed " + session); - } - } else { - // we wanted to resume, but the server refused - // - // Invalidate the session for initial handshake in case - // of reusing next time. - if (isInitialHandshake) { - session.invalidate(); - } - session = null; - if (!enableNewSession) { - 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 - - // check the "extended_master_secret" extension - ExtendedMasterSecretExtension extendedMasterSecretExt = - (ExtendedMasterSecretExtension)mesg.extensions.get( - ExtensionType.EXT_EXTENDED_MASTER_SECRET); - if (extendedMasterSecretExt != null) { - // Is it the expected server extension? - if (!useExtendedMasterSecret || - !mesgVersion.useTLS10PlusSpec() || !requestedToUseEMS) { - fatalSE(Alerts.alert_unsupported_extension, - "Server sent the extended_master_secret " + - "extension improperly"); - } - - // For abbreviated handshake, if the original session did not use - // the "extended_master_secret" extension but the new ServerHello - // contains the extension, the client MUST abort the handshake. - if (resumingSession && (session != null) && - !session.getUseExtendedMasterSecret()) { - fatalSE(Alerts.alert_unsupported_extension, - "Server sent an unexpected extended_master_secret " + - "extension on session resumption"); - } - } else { - if (useExtendedMasterSecret && !allowLegacyMasterSecret) { - // For full handshake, if a client receives a ServerHello - // without the extension, it SHOULD abort the handshake if - // it does not wish to interoperate with legacy servers. - fatalSE(Alerts.alert_handshake_failure, - "Extended Master Secret extension is required"); - } - - if (resumingSession && (session != null)) { - if (session.getUseExtendedMasterSecret()) { - // For abbreviated handshake, if the original session used - // the "extended_master_secret" extension but the new - // ServerHello does not contain the extension, the client - // MUST abort the handshake. - fatalSE(Alerts.alert_handshake_failure, - "Missing Extended Master Secret extension " + - "on session resumption"); - } else if (useExtendedMasterSecret && !allowLegacyResumption) { - // Unlikely, abbreviated handshake should be discarded. - fatalSE(Alerts.alert_handshake_failure, - "Extended Master Secret extension is required"); - } - } - } - - // check the ALPN extension - ALPNExtension serverHelloALPN = - (ALPNExtension) mesg.extensions.get(ExtensionType.EXT_ALPN); - - if (serverHelloALPN != null) { - // Check whether an ALPN extension was sent in ClientHello message - if (!alpnActive) { - fatalSE(Alerts.alert_unsupported_extension, - "Server sent " + ExtensionType.EXT_ALPN + - " extension when not requested by client"); - } - - List protocols = serverHelloALPN.getPeerAPs(); - // Only one application protocol name should be present - String p; - if ((protocols.size() == 1) && - !((p = protocols.get(0)).isEmpty())) { - int i; - for (i = 0; i < localApl.length; i++) { - if (localApl[i].equals(p)) { - break; - } - } - if (i == localApl.length) { - fatalSE(Alerts.alert_handshake_failure, - "Server has selected an application protocol name " + - "which was not offered by the client: " + p); - } - applicationProtocol = p; - } else { - fatalSE(Alerts.alert_handshake_failure, - "Incorrect data in ServerHello " + ExtensionType.EXT_ALPN + - " message"); - } - } else { - applicationProtocol = ""; - } - - if (resumingSession && session != null) { - setHandshakeSessionSE(session); - // Reserve the handshake state if this is a session-resumption - // abbreviated initial handshake. - if (isInitialHandshake) { - session.setAsSessionResumption(true); - } - - return; - } - - // check extensions - for (HelloExtension ext : mesg.extensions.list()) { - ExtensionType type = ext.type; - if (type == ExtensionType.EXT_SERVER_NAME) { - serverNamesAccepted = true; - } else if (type == ExtensionType.EXT_STATUS_REQUEST || - type == ExtensionType.EXT_STATUS_REQUEST_V2) { - // Only enable the stapling feature if the client asserted - // these extensions. - if (sslContext.isStaplingEnabled(true)) { - staplingActive = true; - } else { - fatalSE(Alerts.alert_unexpected_message, "Server set " + - type + " extension when not requested by client"); - } - } else if ((type != ExtensionType.EXT_SUPPORTED_GROUPS) - && (type != ExtensionType.EXT_EC_POINT_FORMATS) - && (type != ExtensionType.EXT_SERVER_NAME) - && (type != ExtensionType.EXT_ALPN) - && (type != ExtensionType.EXT_RENEGOTIATION_INFO) - && (type != ExtensionType.EXT_STATUS_REQUEST) - && (type != ExtensionType.EXT_STATUS_REQUEST_V2) - && (type != ExtensionType.EXT_EXTENDED_MASTER_SECRET)) { - // Note: Better to check client requested extensions rather - // than all supported extensions. - fatalSE(Alerts.alert_unsupported_extension, - "Server sent an unsupported extension: " + type); - } - } - - // Create a new session, we need to do the full handshake - session = new SSLSessionImpl(protocolVersion, cipherSuite, - getLocalSupportedSignAlgs(), - mesg.sessionId, getHostSE(), getPortSE(), - (extendedMasterSecretExt != null)); - session.setRequestedServerNames(requestedServerNames); - session.setNegotiatedMaxFragSize(requestedMFLength); - session.setMaximumPacketSize(maximumPacketSize); - setHandshakeSessionSE(session); - if (debug != null && Debug.isOn("handshake")) { - System.out.println("** " + cipherSuite); - } - } - - /* - * Server's own key was either a signing-only key, or was too - * large for export rules ... this message holds an ephemeral - * RSA key to use for key exchange. - */ - private void serverKeyExchange(RSA_ServerKeyExchange mesg) - throws IOException, GeneralSecurityException { - if (debug != null && Debug.isOn("handshake")) { - mesg.print(System.out); - } - if (!mesg.verify(serverKey, clnt_random, svr_random)) { - fatalSE(Alerts.alert_handshake_failure, - "server key exchange invalid"); - // NOTREACHED - } - ephemeralServerKey = mesg.getPublicKey(); - - // check constraints of RSA PublicKey - if (!algorithmConstraints.permits( - EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), ephemeralServerKey)) { - - throw new SSLHandshakeException("RSA ServerKeyExchange " + - "does not comply to algorithm constraints"); - } - } - - /* - * 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). - * - * Note per section 3 of RFC 7919, if the server is not compatible with - * FFDHE specification, the client MAY decide to continue the connection - * if the selected DHE group is acceptable under local policy, or it MAY - * decide to terminate the connection with a fatal insufficient_security - * (71) alert. The algorithm constraints mechanism is JDK local policy - * used for additional DHE parameters checking. So this implementation - * does not check the server compatibility and just pass to the local - * algorithm constraints checking. The client will continue the - * connection if the server selected DHE group is acceptable by the - * specified algorithm constraints. - */ - private void serverKeyExchange(DH_ServerKeyExchange mesg) - throws IOException { - if (debug != null && Debug.isOn("handshake")) { - mesg.print(System.out); - } - dh = new DHCrypt(mesg.getModulus(), mesg.getBase(), - sslContext.getSecureRandom()); - serverDH = mesg.getServerPublicKey(); - - // check algorithm constraints - dh.checkConstraints(algorithmConstraints, serverDH); - } - - private void serverKeyExchange(ECDH_ServerKeyExchange mesg) - throws IOException { - if (debug != null && Debug.isOn("handshake")) { - mesg.print(System.out); - } - ECPublicKey key = mesg.getPublicKey(); - ecdh = new ECDHCrypt(key.getParams(), sslContext.getSecureRandom()); - ephemeralServerKey = key; - - // check constraints of EC PublicKey - if (!algorithmConstraints.permits( - EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), ephemeralServerKey)) { - - throw new SSLHandshakeException("ECDH ServerKeyExchange " + - "does not comply to algorithm constraints"); - } - } - - /* - * The server's "Hello Done" message is the client's sign that - * it's time to do all the hard work. - */ - private void serverHelloDone(ServerHelloDone mesg) throws IOException { - if (debug != null && Debug.isOn("handshake")) { - mesg.print(System.out); - } - - /* - * FIRST ... if requested, send an appropriate Certificate chain - * to authenticate the client, and remember the associated private - * key to sign the CertificateVerify message. - */ - PrivateKey signingKey = null; - - if (certRequest != null) { - X509ExtendedKeyManager km = sslContext.getX509KeyManager(); - - ArrayList keytypesTmp = new ArrayList<>(4); - - for (int i = 0; i < certRequest.types.length; i++) { - String typeName; - - switch (certRequest.types[i]) { - case CertificateRequest.cct_rsa_sign: - typeName = "RSA"; - break; - - case CertificateRequest.cct_dss_sign: - typeName = "DSA"; - break; - - case CertificateRequest.cct_ecdsa_sign: - // ignore if we do not have EC crypto available - typeName = JsseJce.isEcAvailable() ? "EC" : null; - break; - - // Fixed DH/ECDH client authentication not supported - // - // case CertificateRequest.cct_rsa_fixed_dh: - // case CertificateRequest.cct_dss_fixed_dh: - // case CertificateRequest.cct_rsa_fixed_ecdh: - // case CertificateRequest.cct_ecdsa_fixed_ecdh: - // - // Any other values (currently not used in TLS) - // - // case CertificateRequest.cct_rsa_ephemeral_dh: - // case CertificateRequest.cct_dss_ephemeral_dh: - default: - typeName = null; - break; - } - - if ((typeName != null) && (!keytypesTmp.contains(typeName))) { - keytypesTmp.add(typeName); - } - } - - String alias = null; - int keytypesTmpSize = keytypesTmp.size(); - if (keytypesTmpSize != 0) { - String[] keytypes = - keytypesTmp.toArray(new String[keytypesTmpSize]); - - if (conn != null) { - alias = km.chooseClientAlias(keytypes, - certRequest.getAuthorities(), conn); - } else { - alias = km.chooseEngineClientAlias(keytypes, - certRequest.getAuthorities(), engine); - } - } - - CertificateMsg m1 = null; - if (alias != null) { - X509Certificate[] certs = km.getCertificateChain(alias); - if ((certs != null) && (certs.length != 0)) { - PublicKey publicKey = certs[0].getPublicKey(); - if (publicKey != null) { - m1 = new CertificateMsg(certs); - signingKey = km.getPrivateKey(alias); - session.setLocalPrivateKey(signingKey); - session.setLocalCertificates(certs); - } - } - } - if (m1 == null) { - // - // 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.useTLS10PlusSpec()) { - m1 = new CertificateMsg(new X509Certificate [0]); - } else { - warningSE(Alerts.alert_no_certificate); - } - if (debug != null && Debug.isOn("handshake")) { - System.out.println( - "Warning: no suitable certificate found - " + - "continuing without client authentication"); - } - } - - // - // At last ... send any client certificate chain. - // - 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 - * procedure used is a function of the cipher suite selected; - * one is always needed. - */ - HandshakeMessage m2; - - switch (keyExchange) { - - case K_RSA: - case K_RSA_EXPORT: - if (serverKey == null) { - throw new SSLProtocolException - ("Server did not send certificate message"); - } - - if (!(serverKey instanceof RSAPublicKey)) { - throw new SSLProtocolException - ("Server certificate does not include an RSA key"); - } - - /* - * For RSA key exchange, we randomly generate a new - * pre-master secret and encrypt it with the server's - * public key. Then we save that pre-master secret - * so that we can calculate the keying data later; - * it's a performance speedup not to do that until - * the client's waiting for the server response, but - * more of a speedup for the D-H case. - * - * If the RSA_EXPORT scheme is active, when the public - * key in the server certificate is less than or equal - * to 512 bits in length, use the cert's public key, - * otherwise, the ephemeral one. - */ - PublicKey key; - if (keyExchange == K_RSA) { - key = serverKey; - } else { // K_RSA_EXPORT - if (JsseJce.getRSAKeyLength(serverKey) <= 512) { - // extraneous ephemeralServerKey check done - // above in processMessage() - key = serverKey; - } else { - if (ephemeralServerKey == null) { - throw new SSLProtocolException("Server did not send" + - " a RSA_EXPORT Server Key Exchange message"); - } - key = ephemeralServerKey; - } - } - - m2 = new RSAClientKeyExchange(protocolVersion, maxProtocolVersion, - sslContext.getSecureRandom(), key); - break; - case K_DH_RSA: - case K_DH_DSS: - /* - * For DH Key exchange, we only need to make sure the server - * knows our public key, so we calculate the same pre-master - * secret. - * - * For certs that had DH keys in them, we send an empty - * handshake message (no key) ... we flag this case by - * passing a null "dhPublic" value. - * - * Otherwise we send ephemeral DH keys, unsigned. - */ - // if (useDH_RSA || useDH_DSS) - m2 = new DHClientKeyExchange(); - break; - case K_DHE_RSA: - case K_DHE_DSS: - case K_DH_ANON: - if (dh == null) { - throw new SSLProtocolException - ("Server did not send a DH Server Key Exchange message"); - } - m2 = new DHClientKeyExchange(dh.getPublicKey()); - break; - case K_ECDHE_RSA: - case K_ECDHE_ECDSA: - case K_ECDH_ANON: - if (ecdh == null) { - throw new SSLProtocolException - ("Server did not send a ECDH Server Key Exchange message"); - } - m2 = new ECDHClientKeyExchange(ecdh.getPublicKey()); - break; - case K_ECDH_RSA: - case K_ECDH_ECDSA: - if (serverKey == null) { - throw new SSLProtocolException - ("Server did not send certificate message"); - } - if (serverKey instanceof ECPublicKey == false) { - throw new SSLProtocolException - ("Server certificate does not include an EC key"); - } - ECParameterSpec params = ((ECPublicKey)serverKey).getParams(); - ecdh = new ECDHCrypt(params, sslContext.getSecureRandom()); - m2 = new ECDHClientKeyExchange(ecdh.getPublicKey()); - break; - default: - ClientKeyExchangeService p = - ClientKeyExchangeService.find(keyExchange.name); - if (p == null) { - // somethings very wrong - throw new RuntimeException - ("Unsupported key exchange: " + keyExchange); - } - String sniHostname = null; - for (SNIServerName serverName : requestedServerNames) { - if (serverName instanceof SNIHostName) { - sniHostname = ((SNIHostName) serverName).getAsciiName(); - break; - } - } - - ClientKeyExchange exMsg = null; - if (sniHostname != null) { - // use first requested SNI hostname - try { - exMsg = p.createClientExchange( - sniHostname, getAccSE(), protocolVersion, - sslContext.getSecureRandom()); - } catch(IOException e) { - if (serverNamesAccepted) { - // server accepted requested SNI hostname, - // so it must be used - throw e; - } - // fallback to using hostname - if (debug != null && Debug.isOn("handshake")) { - System.out.println( - "Warning, cannot use Server Name Indication: " - + e.getMessage()); - } - } - } - - if (exMsg == null) { - String hostname = getHostSE(); - if (hostname == null) { - throw new IOException("Hostname is required" + - " to use " + keyExchange + " key exchange"); - } - exMsg = p.createClientExchange( - hostname, getAccSE(), protocolVersion, - sslContext.getSecureRandom()); - } - - // Record the principals involved in exchange - session.setPeerPrincipal(exMsg.getPeerPrincipal()); - session.setLocalPrincipal(exMsg.getLocalPrincipal()); - m2 = exMsg; - break; - } - 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.flush(); - - /* - * We deferred calculating the master secret and this connection's - * keying data; we do it now. Deferring this calculation is good - * from a performance point of view, since it lets us do it during - * some time that network delays and the server's own calculations - * would otherwise cause to be "dead" in the critical path. - */ - SecretKey preMasterSecret; - switch (keyExchange) { - case K_RSA: - case K_RSA_EXPORT: - preMasterSecret = ((RSAClientKeyExchange)m2).preMaster; - break; - case K_DHE_RSA: - case K_DHE_DSS: - case K_DH_ANON: - preMasterSecret = dh.getAgreedSecret(serverDH, true); - break; - case K_ECDHE_RSA: - case K_ECDHE_ECDSA: - case K_ECDH_ANON: - preMasterSecret = ecdh.getAgreedSecret(ephemeralServerKey); - break; - case K_ECDH_RSA: - case K_ECDH_ECDSA: - preMasterSecret = ecdh.getAgreedSecret(serverKey); - break; - default: - if (ClientKeyExchangeService.find(keyExchange.name) != null) { - preMasterSecret = - ((ClientKeyExchange) m2).clientKeyExchange(); - } else { - throw new IOException("Internal error: unknown key exchange " - + keyExchange); - } - } - - calculateKeys(preMasterSecret, null); - - /* - * FOURTH, if we sent a Certificate, we need to send a signed - * CertificateVerify (unless the key in the client's certificate - * was a Diffie-Hellman key). - * - * This uses a hash of the previous handshake messages ... either - * a nonfinal one (if the particular implementation supports it) - * or else using the third element in the arrays of hashes being - * computed. - */ - if (signingKey != null) { - CertificateVerify m3; - try { - SignatureAndHashAlgorithm preferableSignatureAlgorithm = null; - if (protocolVersion.useTLS12PlusSpec()) { - preferableSignatureAlgorithm = - SignatureAndHashAlgorithm.getPreferableAlgorithm( - getPeerSupportedSignAlgs(), - signingKey.getAlgorithm(), signingKey); - - if (preferableSignatureAlgorithm == null) { - throw new SSLHandshakeException( - "No supported signature algorithm"); - } - - String hashAlg = - SignatureAndHashAlgorithm.getHashAlgorithmName( - preferableSignatureAlgorithm); - if (hashAlg == null || hashAlg.length() == 0) { - throw new SSLHandshakeException( - "No supported hash algorithm"); - } - } - - m3 = new CertificateVerify(protocolVersion, handshakeHash, - signingKey, session.getMasterSecret(), - sslContext.getSecureRandom(), - preferableSignatureAlgorithm); - } catch (GeneralSecurityException e) { - fatalSE(Alerts.alert_handshake_failure, - "Error signing certificate verify", e); - // NOTREACHED, make compiler happy - m3 = null; - } - if (debug != null && Debug.isOn("handshake")) { - m3.print(System.out); - } - m3.write(output); - 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 - * 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(); - } - - /* - * Reset the handshake state if this is not an initial handshake. - */ - if (!isInitialHandshake) { - session.setAsSessionResumption(false); - } - - /* - * 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) { - sendChangeCipherAndFinish(true); - } else { - handshakeFinished = 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 { - - // 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 - * 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(); - } - } - - - /* - * Returns a ClientHello message to kickstart renegotiations - */ - @Override - 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) { - // If unsafe server certificate change is not allowed, reserve - // current server certificates if the previous handshake is a - // session-resumption abbreviated initial handshake. - if (!allowUnsafeServerCertChange && session.isSessionResumption()) { - try { - // If existing, peer certificate chain cannot be null. - reservedServerCerts = - (X509Certificate[])session.getPeerCertificates(); - } catch (SSLPeerUnverifiedException puve) { - // Maybe not certificate-based, ignore the exception. - } - } - - if (!session.isRejoinable()) { - 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) && useExtendedMasterSecret) { - boolean isTLS10Plus = sessionVersion.useTLS10PlusSpec(); - if (isTLS10Plus && !session.getUseExtendedMasterSecret()) { - if (!allowLegacyResumption) { - // perform full handshake instead - // - // The client SHOULD NOT offer an abbreviated handshake - // to resume a session that does not use an extended - // master secret. Instead, it SHOULD offer a full - // handshake. - session = null; - } - } - - if ((session != null) && !allowUnsafeServerCertChange) { - // It is fine to move on with abbreviate handshake if - // endpoint identification is enabled. - String identityAlg = getEndpointIdentificationAlgorithmSE(); - if ((identityAlg == null || identityAlg.length() == 0)) { - if (isTLS10Plus) { - if (!session.getUseExtendedMasterSecret()) { - // perform full handshake instead - session = null; - } // Otherwise, use extended master secret. - } else { - // The extended master secret extension does not - // apply to SSL 3.0. Perform a full handshake - // instead. - // - // Note that the useExtendedMasterSecret is - // extended to protect SSL 3.0 connections, - // by discarding abbreviate handshake. - 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 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 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, isDTLS); - - // Add named groups extension for ECDHE and FFDHE if necessary. - SupportedGroupsExtension sge = - SupportedGroupsExtension.createExtension( - algorithmConstraints, - cipherSuites, enableFFDHE); - if (sge != null) { - clientHelloMessage.extensions.add(sge); - // Add elliptic point format extensions - if (cipherSuites.contains(NamedGroupType.NAMED_GROUP_ECDHE)) { - clientHelloMessage.extensions.add( - EllipticPointFormatsExtension.DEFAULT); - } - } - - // add signature_algorithm extension - if (maxProtocolVersion.useTLS12PlusSpec()) { - // we will always send the signature_algorithm extension - Collection localSignAlgs = - getLocalSupportedSignAlgs(); - if (localSignAlgs.isEmpty()) { - throw new SSLHandshakeException( - "No supported signature algorithm"); - } - - clientHelloMessage.addSignatureAlgorithmsExtension(localSignAlgs); - } - - // add Extended Master Secret extension - if (useExtendedMasterSecret && maxProtocolVersion.useTLS10PlusSpec()) { - if ((session == null) || session.getUseExtendedMasterSecret()) { - clientHelloMessage.addExtendedMasterSecretExtension(); - requestedToUseEMS = true; - } - } - - // add server_name extension - if (enableSNIExtension) { - if (session != null) { - requestedServerNames = session.getRequestedServerNames(); - } else { - requestedServerNames = serverNames; - } - - 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; - } - } - - // Add status_request and status_request_v2 extensions - if (sslContext.isStaplingEnabled(true)) { - clientHelloMessage.addCertStatusReqListV2Extension(); - clientHelloMessage.addCertStatusRequestExtension(); - } - - // Add ALPN extension - if (localApl != null && localApl.length > 0) { - clientHelloMessage.addALPNExtension(localApl); - alpnActive = true; - } - - // 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); - } - - if (isDTLS) { - // Cookie exchange need to reserve the initial ClientHello message. - initialClientHelloMsg = clientHelloMessage; - } - - return clientHelloMessage; - } - - /* - * Fault detected during handshake. - */ - @Override - 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"); - } - - // Allow server certificate change in client side during renegotiation - // after a session-resumption abbreviated initial handshake? - // - // DO NOT need to check allowUnsafeServerCertChange here. We only - // reserve server certificates when allowUnsafeServerCertChange is - // flase. - // - // Allow server certificate change if it is negotiated to use the - // extended master secret. - if ((reservedServerCerts != null) && - !session.getUseExtendedMasterSecret()) { - // It is not necessary to check the certificate update if endpoint - // identification is enabled. - String identityAlg = getEndpointIdentificationAlgorithmSE(); - if ((identityAlg == null || identityAlg.length() == 0) && - !isIdentityEquivalent(peerCerts[0], reservedServerCerts[0])) { - - fatalSE(Alerts.alert_bad_certificate, - "server certificate change is restricted " + - "during renegotiation"); - } - } - - // ask the trust manager to verify the chain - if (staplingActive) { - // Defer the certificate check until after we've received the - // CertificateStatus message. If that message doesn't come in - // immediately following this message we will execute the check - // directly from processMessage before any other SSL/TLS processing. - deferredCerts = peerCerts; - } else { - // We're not doing stapling, so perform the check right now - checkServerCerts(peerCerts); - } - } - - /** - * If certificate status stapling has been enabled, the server will send - * one or more status messages to the client. - * - * @param mesg a {@code CertificateStatus} object built from the data - * sent by the server. - * - * @throws IOException if any parsing errors occur. - */ - private void certificateStatus(CertificateStatus mesg) throws IOException { - if (debug != null && Debug.isOn("handshake")) { - mesg.print(System.out); - } - - // Perform the certificate check using the deferred certificates - // and responses that we have obtained. - session.setStatusResponses(mesg.getResponses()); - checkServerCerts(deferredCerts); - } - - /* - * Whether the certificates can represent the same identity? - * - * The certificates can be used to represent the same identity: - * 1. If the subject alternative names of IP address are present in - * both certificates, they should be identical; otherwise, - * 2. if the subject alternative names of DNS name are present in - * both certificates, they should be identical; otherwise, - * 3. if the subject fields are present in both certificates, the - * certificate subjects and issuers should be identical. - */ - private static boolean isIdentityEquivalent(X509Certificate thisCert, - X509Certificate prevCert) { - if (thisCert.equals(prevCert)) { - return true; - } - - // check subject alternative names - Collection> thisSubjectAltNames = null; - try { - thisSubjectAltNames = thisCert.getSubjectAlternativeNames(); - } catch (CertificateParsingException cpe) { - if (debug != null && Debug.isOn("handshake")) { - System.out.println( - "Attempt to obtain subjectAltNames extension failed!"); - } - } - - Collection> prevSubjectAltNames = null; - try { - prevSubjectAltNames = prevCert.getSubjectAlternativeNames(); - } catch (CertificateParsingException cpe) { - if (debug != null && Debug.isOn("handshake")) { - System.out.println( - "Attempt to obtain subjectAltNames extension failed!"); - } - } - - if ((thisSubjectAltNames != null) && (prevSubjectAltNames != null)) { - // check the iPAddress field in subjectAltName extension - Collection thisSubAltIPAddrs = - getSubjectAltNames(thisSubjectAltNames, ALTNAME_IP); - Collection prevSubAltIPAddrs = - getSubjectAltNames(prevSubjectAltNames, ALTNAME_IP); - if ((thisSubAltIPAddrs != null) && (prevSubAltIPAddrs != null) && - (isEquivalent(thisSubAltIPAddrs, prevSubAltIPAddrs))) { - - return true; - } - - // check the dNSName field in subjectAltName extension - Collection thisSubAltDnsNames = - getSubjectAltNames(thisSubjectAltNames, ALTNAME_DNS); - Collection prevSubAltDnsNames = - getSubjectAltNames(prevSubjectAltNames, ALTNAME_DNS); - if ((thisSubAltDnsNames != null) && (prevSubAltDnsNames != null) && - (isEquivalent(thisSubAltDnsNames, prevSubAltDnsNames))) { - - return true; - } - } - - // check the certificate subject and issuer - X500Principal thisSubject = thisCert.getSubjectX500Principal(); - X500Principal prevSubject = prevCert.getSubjectX500Principal(); - X500Principal thisIssuer = thisCert.getIssuerX500Principal(); - X500Principal prevIssuer = prevCert.getIssuerX500Principal(); - if (!thisSubject.getName().isEmpty() && - !prevSubject.getName().isEmpty() && - thisSubject.equals(prevSubject) && - thisIssuer.equals(prevIssuer)) { - return true; - } - - return false; - } - - /* - * Returns the subject alternative name of the specified type in the - * subjectAltNames extension of a certificate. - * - * Note that only those subjectAltName types that use String data - * should be passed into this function. - */ - private static Collection getSubjectAltNames( - Collection> subjectAltNames, int type) { - - HashSet subAltDnsNames = null; - for (List subjectAltName : subjectAltNames) { - int subjectAltNameType = (Integer)subjectAltName.get(0); - if (subjectAltNameType == type) { - String subAltDnsName = (String)subjectAltName.get(1); - if ((subAltDnsName != null) && !subAltDnsName.isEmpty()) { - if (subAltDnsNames == null) { - subAltDnsNames = - new HashSet<>(subjectAltNames.size()); - } - subAltDnsNames.add(subAltDnsName); - } - } - } - - return subAltDnsNames; - } - - private static boolean isEquivalent(Collection thisSubAltNames, - Collection prevSubAltNames) { - - for (String thisSubAltName : thisSubAltNames) { - for (String prevSubAltName : prevSubAltNames) { - // Only allow the exactly match. Check no wildcard character. - if (thisSubAltName.equalsIgnoreCase(prevSubAltName)) { - return true; - } - } - } - - return false; - } - - /** - * Perform client-side checking of server certificates. - * - * @param certs an array of {@code X509Certificate} objects presented - * by the server in the ServerCertificate message. - * - * @throws IOException if a failure occurs during validation or - * the trust manager associated with the {@code SSLContext} is not - * an {@code X509ExtendedTrustManager}. - */ - private void checkServerCerts(X509Certificate[] certs) - throws IOException { - X509TrustManager tm = sslContext.getX509TrustManager(); - - // 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; - } - - try { - if (tm instanceof X509ExtendedTrustManager) { - if (conn != null) { - ((X509ExtendedTrustManager)tm).checkServerTrusted( - certs.clone(), - keyExchangeString, - conn); - } else { - ((X509ExtendedTrustManager)tm).checkServerTrusted( - certs.clone(), - keyExchangeString, - engine); - } - } else { - // Unlikely to happen, because we have wrapped the old - // X509TrustManager with the new X509ExtendedTrustManager. - throw new CertificateException( - "Improper X509TrustManager implementation"); - } - - // Once the server certificate chain has been validated, set - // the certificate chain in the TLS session. - session.setPeerCertificates(certs); - } catch (CertificateException ce) { - fatalSE(getCertificateAlert(ce), ce); - } - } - - /** - * When a failure happens during certificate checking from an - * {@link X509TrustManager}, determine what TLS alert description to use. - * - * @param cexc The exception thrown by the {@link X509TrustManager} - * - * @return A byte value corresponding to a TLS alert description number. - */ - private byte getCertificateAlert(CertificateException cexc) { - // The specific reason for the failure will determine how to - // set the alert description value - byte alertDesc = Alerts.alert_certificate_unknown; - - Throwable baseCause = cexc.getCause(); - if (baseCause instanceof CertPathValidatorException) { - CertPathValidatorException cpve = - (CertPathValidatorException)baseCause; - Reason reason = cpve.getReason(); - if (reason == BasicReason.REVOKED) { - alertDesc = staplingActive ? - Alerts.alert_bad_certificate_status_response : - Alerts.alert_certificate_revoked; - } else if (reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) { - alertDesc = staplingActive ? - Alerts.alert_bad_certificate_status_response : - Alerts.alert_certificate_unknown; - } - } - - return alertDesc; - } -} - --- /dev/null 2018-05-11 10:42:23.849000000 -0700 +++ new/src/java.base/share/classes/sun/security/ssl/ClientHandshakeContext.java 2018-05-11 15:08:55.298344900 -0700 @@ -0,0 +1,107 @@ +/* + * Copyright (c) 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.security.cert.X509Certificate; + +import sun.security.ssl.ClientHello.ClientHelloMessage; + +class ClientHandshakeContext extends HandshakeContext { + /* + * Allow unsafe server certificate change? + * + * Server certificate change during SSL/TLS renegotiation may be considered + * unsafe, as described in the Triple Handshake attacks: + * + * https://secure-resumption.com/tlsauth.pdf + * + * Endpoint identification (See + * SSLParameters.getEndpointIdentificationAlgorithm()) is a pretty nice + * guarantee that the server certificate change in renegotiation is legal. + * However, endpoing identification is only enabled for HTTPS and LDAP + * over SSL/TLS by default. It is not enough to protect SSL/TLS + * connections other than HTTPS and LDAP. + * + * The renegotiation indication extension (See RFC 5746) is a pretty + * strong guarantee that the endpoints on both client and server sides + * are identical on the same connection. However, the Triple Handshake + * attacks can bypass this guarantee if there is a session-resumption + * handshake between the initial full handshake and the renegotiation + * full handshake. + * + * Server certificate change may be unsafe and should be restricted if + * endpoint identification is not enabled and the previous handshake is + * a session-resumption abbreviated initial handshake, unless the + * identities represented by both certificates can be regraded as the + * same (See isIdentityEquivalent()). + * + * Considering the compatibility impact and the actual requirements to + * support server certificate change in practice, the system property, + * jdk.tls.allowUnsafeServerCertChange, is used to define whether unsafe + * server certificate change in renegotiation is allowed or not. The + * default value of the system property is "false". To mitigate the + * compactibility impact, applications may want to set the system + * property to "true" at their own risk. + * + * If the value of the system property is "false", server certificate + * change in renegotiation after a session-resumption abbreviated initial + * handshake is restricted (See isIdentityEquivalent()). + * + * If the system property is set to "true" explicitly, the restriction on + * server certificate change in renegotiation is disabled. + */ + static final boolean allowUnsafeServerCertChange = + Utilities.getBooleanProperty( + "jdk.tls.allowUnsafeServerCertChange", false); + + /* + * 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. + */ + X509Certificate[] reservedServerCerts = null; + + X509Certificate[] deferredCerts; + + ClientHelloMessage initialClientHelloMsg = null; + + ClientHandshakeContext(SSLContextImpl sslContext, + TransportContext conContext) throws IOException { + super(sslContext, conContext); + } + + @Override + void kickstart() throws IOException { + if (kickstartMessageDelivered) { + return; + } + + SSLHandshake.kickstart(this); + kickstartMessageDelivered = true; + } +}