| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
package sun.security.ssl;  | 
 | 
 | 
 | 
import java.io.IOException;  | 
 | 
import java.nio.ByteBuffer;  | 
 | 
import java.security.AccessController;  | 
 | 
import java.security.Principal;  | 
 | 
import java.security.PrivilegedActionException;  | 
 | 
import java.security.PrivilegedExceptionAction;  | 
 | 
import java.security.SecureRandom;  | 
 | 
import java.security.cert.X509Certificate;  | 
 | 
import java.text.MessageFormat;  | 
 | 
import java.util.Arrays;  | 
 | 
import java.util.Collections;  | 
 | 
import java.util.LinkedList;  | 
 | 
import java.util.List;  | 
 | 
import java.util.Locale;  | 
 | 
import java.util.Objects;  | 
 | 
import javax.net.ssl.SSLException;  | 
 | 
import javax.net.ssl.SSLHandshakeException;  | 
 | 
import javax.net.ssl.SSLPeerUnverifiedException;  | 
 | 
import javax.net.ssl.SSLProtocolException;  | 
 | 
import javax.security.auth.Subject;  | 
 | 
 | 
 | 
import static sun.security.ssl.CipherSuite.KeyExchange.K_KRB5;  | 
 | 
import static sun.security.ssl.CipherSuite.KeyExchange.K_KRB5_EXPORT;  | 
 | 
import static sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED;  | 
 | 
import sun.security.ssl.SSLHandshake.HandshakeMessage;  | 
 | 
import sun.security.ssl.SupportedVersionsExtension.CHSupportedVersionsSpec;  | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
final class ClientHello { | 
 | 
    static final SSLProducer kickstartProducer =  | 
 | 
        new ClientHelloKickstartProducer();  | 
 | 
    static final SSLConsumer handshakeConsumer =  | 
 | 
        new ClientHelloConsumer();  | 
 | 
    static final HandshakeProducer handshakeProducer =  | 
 | 
        new ClientHelloProducer();  | 
 | 
 | 
 | 
    private static final HandshakeConsumer t12HandshakeConsumer =  | 
 | 
            new T12ClientHelloConsumer();  | 
 | 
    private static final HandshakeConsumer t13HandshakeConsumer =  | 
 | 
            new T13ClientHelloConsumer();  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    static final class ClientHelloMessage extends HandshakeMessage { | 
 | 
        final int                   clientVersion;  | 
 | 
        final RandomCookie          clientRandom;  | 
 | 
        final SessionId             sessionId;  | 
 | 
        final int[]                 cipherSuiteIds;  | 
 | 
        final List<CipherSuite>     cipherSuites;     | 
 | 
        final byte[]                compressionMethod;  | 
 | 
        final SSLExtensions         extensions;  | 
 | 
 | 
 | 
        private static final byte[]  NULL_COMPRESSION = new byte[] {0}; | 
 | 
 | 
 | 
        ClientHelloMessage(HandshakeContext handshakeContext,  | 
 | 
                int clientVersion, SessionId sessionId,  | 
 | 
                List<CipherSuite> cipherSuites, SecureRandom generator) { | 
 | 
            super(handshakeContext);  | 
 | 
 | 
 | 
            this.clientVersion = clientVersion;  | 
 | 
            this.clientRandom = new RandomCookie(generator);  | 
 | 
            this.sessionId = sessionId;  | 
 | 
 | 
 | 
            this.cipherSuites = cipherSuites;  | 
 | 
            this.cipherSuiteIds = getCipherSuiteIds(cipherSuites);  | 
 | 
            this.extensions = new SSLExtensions(this);  | 
 | 
 | 
 | 
              | 
 | 
            this.compressionMethod = NULL_COMPRESSION;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        static void readPartial(TransportContext tc,  | 
 | 
                ByteBuffer m) throws IOException { | 
 | 
              | 
 | 
            Record.getInt16(m);  | 
 | 
 | 
 | 
            new RandomCookie(m);  | 
 | 
 | 
 | 
              | 
 | 
            Record.getBytes8(m);  | 
 | 
 | 
 | 
              | 
 | 
            Record.getBytes16(m);  | 
 | 
              | 
 | 
            Record.getBytes8(m);  | 
 | 
              | 
 | 
            if (m.remaining() >= 2) { | 
 | 
                int remaining = Record.getInt16(m);  | 
 | 
                while (remaining > 0) { | 
 | 
                    int id = Record.getInt16(m);  | 
 | 
                    int extLen = Record.getInt16(m);  | 
 | 
                    remaining -= extLen + 4;  | 
 | 
 | 
 | 
                    if (id == SSLExtension.CH_PRE_SHARED_KEY.id) { | 
 | 
                          | 
 | 
                        if (remaining > 0) { | 
 | 
                            throw tc.fatal(Alert.ILLEGAL_PARAMETER,  | 
 | 
                                    "pre_shared_key extension is not last");  | 
 | 
                        }  | 
 | 
                          | 
 | 
                        Record.getBytes16(m);  | 
 | 
                        return;  | 
 | 
                    } else { | 
 | 
                        m.position(m.position() + extLen);  | 
 | 
 | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }   // Otherwise, ignore the remaining bytes.  | 
 | 
        }  | 
 | 
 | 
 | 
        ClientHelloMessage(HandshakeContext handshakeContext, ByteBuffer m,  | 
 | 
                SSLExtension[] supportedExtensions) throws IOException { | 
 | 
            super(handshakeContext);  | 
 | 
 | 
 | 
            this.clientVersion = ((m.get() & 0xFF) << 8) | (m.get() & 0xFF);  | 
 | 
            this.clientRandom = new RandomCookie(m);  | 
 | 
            this.sessionId = new SessionId(Record.getBytes8(m));  | 
 | 
            try { | 
 | 
                sessionId.checkLength(clientVersion);  | 
 | 
            } catch (SSLProtocolException ex) { | 
 | 
                throw handshakeContext.conContext.fatal(  | 
 | 
                        Alert.ILLEGAL_PARAMETER, ex);  | 
 | 
            }  | 
 | 
            byte[] encodedIds = Record.getBytes16(m);  | 
 | 
            if (encodedIds.length == 0 || (encodedIds.length & 0x01) != 0) { | 
 | 
                throw handshakeContext.conContext.fatal(  | 
 | 
                        Alert.ILLEGAL_PARAMETER,  | 
 | 
                        "Invalid ClientHello message");  | 
 | 
            }  | 
 | 
 | 
 | 
            this.cipherSuiteIds = new int[encodedIds.length >> 1];  | 
 | 
            for (int i = 0, j = 0; i < encodedIds.length; i++, j++) { | 
 | 
                cipherSuiteIds[j] =  | 
 | 
                    ((encodedIds[i++] & 0xFF) << 8) | (encodedIds[i] & 0xFF);  | 
 | 
            }  | 
 | 
            this.cipherSuites = getCipherSuites(cipherSuiteIds);  | 
 | 
 | 
 | 
            this.compressionMethod = Record.getBytes8(m);  | 
 | 
              | 
 | 
            if (m.hasRemaining()) { | 
 | 
                this.extensions =  | 
 | 
                        new SSLExtensions(this, m, supportedExtensions);  | 
 | 
            } else { | 
 | 
                this.extensions = new SSLExtensions(this);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        byte[] getHeaderBytes() { | 
 | 
            HandshakeOutStream hos = new HandshakeOutStream(null);  | 
 | 
            try { | 
 | 
                  | 
 | 
                hos.putInt8((byte)((clientVersion >>> 8) & 0xFF));  | 
 | 
                hos.putInt8((byte)(clientVersion & 0xFF));  | 
 | 
                hos.write(clientRandom.randomBytes, 0, 32);  | 
 | 
                hos.putBytes8(sessionId.getId());  | 
 | 
                hos.putBytes16(getEncodedCipherSuites());  | 
 | 
                hos.putBytes8(compressionMethod);  | 
 | 
            } catch (IOException ioe) { | 
 | 
                // unlikely  | 
 | 
            }  | 
 | 
 | 
 | 
            return hos.toByteArray();  | 
 | 
        }  | 
 | 
 | 
 | 
        private static int[] getCipherSuiteIds(  | 
 | 
                List<CipherSuite> cipherSuites) { | 
 | 
            if (cipherSuites != null) { | 
 | 
                int[] ids = new int[cipherSuites.size()];  | 
 | 
                int i = 0;  | 
 | 
                for (CipherSuite cipherSuite : cipherSuites) { | 
 | 
                    ids[i++] = cipherSuite.id;  | 
 | 
                }  | 
 | 
 | 
 | 
                return ids;  | 
 | 
            }  | 
 | 
 | 
 | 
            return new int[0];  | 
 | 
        }  | 
 | 
 | 
 | 
        private static List<CipherSuite> getCipherSuites(int[] ids) { | 
 | 
            List<CipherSuite> cipherSuites = new LinkedList<>();  | 
 | 
            for (int id : ids) { | 
 | 
                CipherSuite cipherSuite = CipherSuite.valueOf(id);  | 
 | 
                if (cipherSuite != null) { | 
 | 
                    cipherSuites.add(cipherSuite);  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            return Collections.unmodifiableList(cipherSuites);  | 
 | 
        }  | 
 | 
 | 
 | 
        private List<String> getCipherSuiteNames() { | 
 | 
            List<String> names = new LinkedList<>();  | 
 | 
            for (int id : cipherSuiteIds) { | 
 | 
                names.add(CipherSuite.nameOf(id) +  | 
 | 
                        "(" + Utilities.byte16HexString(id) + ")");            } | 
 | 
 | 
 | 
            return names;  | 
 | 
        }  | 
 | 
 | 
 | 
        private byte[] getEncodedCipherSuites() { | 
 | 
            byte[] encoded = new byte[cipherSuiteIds.length << 1];  | 
 | 
            int i = 0;  | 
 | 
            for (int id : cipherSuiteIds) { | 
 | 
                encoded[i++] = (byte)(id >> 8);  | 
 | 
                encoded[i++] = (byte)id;  | 
 | 
            }  | 
 | 
            return encoded;  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public SSLHandshake handshakeType() { | 
 | 
            return SSLHandshake.CLIENT_HELLO;  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public int messageLength() { | 
 | 
              | 
 | 
 | 
 | 
 | 
 | 
             */  | 
 | 
            return (2 + 32 + 1 + 2 + 1  | 
 | 
                + sessionId.length()          | 
 | 
                + (cipherSuiteIds.length * 2)  | 
 | 
                + compressionMethod.length)  | 
 | 
                + extensions.length();        | 
 | 
                                            // extensions is mandatory.  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public void send(HandshakeOutStream hos) throws IOException { | 
 | 
            sendCore(hos);  | 
 | 
            extensions.send(hos);         | 
 | 
                                        // extensions is mandatory.  | 
 | 
        }  | 
 | 
 | 
 | 
        void sendCore(HandshakeOutStream hos) throws IOException { | 
 | 
            hos.putInt8((byte) (clientVersion >>> 8));  | 
 | 
            hos.putInt8((byte) clientVersion);  | 
 | 
            hos.write(clientRandom.randomBytes, 0, 32);  | 
 | 
            hos.putBytes8(sessionId.getId());  | 
 | 
            hos.putBytes16(getEncodedCipherSuites());  | 
 | 
            hos.putBytes8(compressionMethod);  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public String toString() { | 
 | 
            MessageFormat messageFormat = new MessageFormat(  | 
 | 
                "\"ClientHello\": '{'\n" + | 
 | 
                "  \"client version\"      : \"{0}\",\n" + | 
 | 
                "  \"random\"              : \"{1}\",\n" + | 
 | 
                "  \"session id\"          : \"{2}\",\n" + | 
 | 
                "  \"cipher suites\"       : \"{3}\",\n" + | 
 | 
                "  \"compression methods\" : \"{4}\",\n" + | 
 | 
                "  \"extensions\"          : [\n" +  | 
 | 
                "{5}\n" + | 
 | 
                "  ]\n" +  | 
 | 
                "'}'",  | 
 | 
                Locale.ENGLISH);  | 
 | 
            Object[] messageFields = { | 
 | 
                ProtocolVersion.nameOf(clientVersion),  | 
 | 
                Utilities.toHexString(clientRandom.randomBytes),  | 
 | 
                sessionId.toString(),  | 
 | 
                getCipherSuiteNames().toString(),  | 
 | 
                Utilities.toHexString(compressionMethod),  | 
 | 
                Utilities.indent(Utilities.indent(extensions.toString()))  | 
 | 
            };  | 
 | 
 | 
 | 
            return messageFormat.format(messageFields);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private static final  | 
 | 
            class ClientHelloKickstartProducer implements SSLProducer { | 
 | 
          | 
 | 
        private ClientHelloKickstartProducer() { | 
 | 
            // blank  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        @Override  | 
 | 
        public byte[] produce(ConnectionContext context) throws IOException { | 
 | 
              | 
 | 
            ClientHandshakeContext chc = (ClientHandshakeContext)context;  | 
 | 
 | 
 | 
              | 
 | 
            chc.handshakeProducers.remove(SSLHandshake.CLIENT_HELLO.id);  | 
 | 
 | 
 | 
              | 
 | 
            ProtocolVersion maxProtocolVersion = chc.maximumActiveProtocol;  | 
 | 
 | 
 | 
              | 
 | 
            SessionId sessionId = new SessionId(new byte[0]);  | 
 | 
 | 
 | 
              | 
 | 
            List<CipherSuite> cipherSuites = chc.activeCipherSuites;  | 
 | 
 | 
 | 
            //  | 
 | 
            // Try to resume an existing session.  | 
 | 
              | 
 | 
            SSLSessionContextImpl ssci = (SSLSessionContextImpl)  | 
 | 
                    chc.sslContext.engineGetClientSessionContext();  | 
 | 
            SSLSessionImpl session = ssci.get(  | 
 | 
                    chc.conContext.transport.getPeerHost(),  | 
 | 
                    chc.conContext.transport.getPeerPort());  | 
 | 
            if (session != null) { | 
 | 
                // If unsafe server certificate change is not allowed, reserve  | 
 | 
                // current server certificates if the previous handshake is a  | 
 | 
                  | 
 | 
                if (!ClientHandshakeContext.allowUnsafeServerCertChange &&  | 
 | 
                        session.isSessionResumption()) { | 
 | 
                    try { | 
 | 
                          | 
 | 
                        chc.reservedServerCerts =  | 
 | 
                            (X509Certificate[])session.getPeerCertificates();  | 
 | 
                    } catch (SSLPeerUnverifiedException puve) { | 
 | 
                        // Maybe not certificate-based, ignore the exception.  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                if (!session.isRejoinable()) { | 
 | 
                    session = null;  | 
 | 
                    if (SSLLogger.isOn &&  | 
 | 
                            SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                        SSLLogger.finest(  | 
 | 
                            "Can't resume, the session is not rejoinable");  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            CipherSuite sessionSuite = null;  | 
 | 
            if (session != null) { | 
 | 
                sessionSuite = session.getSuite();  | 
 | 
                if (!chc.isNegotiable(sessionSuite)) { | 
 | 
                    session = null;  | 
 | 
                    if (SSLLogger.isOn &&  | 
 | 
                            SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                        SSLLogger.finest(  | 
 | 
                            "Can't resume, unavailable session cipher suite");  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            ProtocolVersion sessionVersion = null;  | 
 | 
            if (session != null) { | 
 | 
                sessionVersion = session.getProtocolVersion();  | 
 | 
                if (!chc.isNegotiable(sessionVersion)) { | 
 | 
                    session = null;  | 
 | 
                    if (SSLLogger.isOn &&  | 
 | 
                            SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                        SSLLogger.finest(  | 
 | 
                            "Can't resume, unavailable protocol version");  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            if (session != null &&  | 
 | 
                !sessionVersion.useTLS13PlusSpec() &&  | 
 | 
                SSLConfiguration.useExtendedMasterSecret) { | 
 | 
 | 
 | 
                boolean isEmsAvailable = chc.sslConfig.isAvailable(  | 
 | 
                    SSLExtension.CH_EXTENDED_MASTER_SECRET, sessionVersion);  | 
 | 
                if (isEmsAvailable && !session.useExtendedMasterSecret &&  | 
 | 
                        !SSLConfiguration.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  | 
 | 
                      | 
 | 
                     session = null;  | 
 | 
                }  | 
 | 
 | 
 | 
                if ((session != null) &&  | 
 | 
                        !ClientHandshakeContext.allowUnsafeServerCertChange) { | 
 | 
                    // It is fine to move on with abbreviate handshake if  | 
 | 
                      | 
 | 
                    String identityAlg = chc.sslConfig.identificationProtocol;  | 
 | 
                    if (identityAlg == null || identityAlg.isEmpty()) { | 
 | 
                        if (isEmsAvailable) { | 
 | 
                            if (!session.useExtendedMasterSecret) { | 
 | 
                                  | 
 | 
                                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,  | 
 | 
                              | 
 | 
                            session = null;  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            // ensure that the endpoint identification algorithm matches the  | 
 | 
              | 
 | 
            String identityAlg = chc.sslConfig.identificationProtocol;  | 
 | 
            if (session != null && identityAlg != null) { | 
 | 
                String sessionIdentityAlg =  | 
 | 
                    session.getIdentificationProtocol();  | 
 | 
                if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { | 
 | 
                    if (SSLLogger.isOn &&  | 
 | 
                    SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                        SSLLogger.finest("Can't resume, endpoint id" + | 
 | 
                            " algorithm does not match, requested: " +  | 
 | 
                            identityAlg + ", cached: " + sessionIdentityAlg);  | 
 | 
                    }  | 
 | 
                    session = null;  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            if (session != null) { | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                    SSLLogger.finest("Try resuming session", session); | 
 | 
                }  | 
 | 
 | 
 | 
                  | 
 | 
                if (!session.getProtocolVersion().useTLS13PlusSpec()) { | 
 | 
                    sessionId = session.getSessionId();  | 
 | 
                }  | 
 | 
                if (!maxProtocolVersion.equals(sessionVersion)) { | 
 | 
                    maxProtocolVersion = sessionVersion;  | 
 | 
 | 
 | 
                    // Update protocol version number in underlying socket and  | 
 | 
                    // handshake output stream, so that the output records  | 
 | 
                      | 
 | 
                    chc.setVersion(sessionVersion);  | 
 | 
                }  | 
 | 
 | 
 | 
                // If no new session is allowed, force use of the previous  | 
 | 
                // session ciphersuite, and add the renegotiation SCSV if  | 
 | 
                  | 
 | 
                if (!chc.sslConfig.enableSessionCreation) { | 
 | 
                    if (!chc.conContext.isNegotiated &&  | 
 | 
                        !sessionVersion.useTLS13PlusSpec() &&  | 
 | 
                        cipherSuites.contains(  | 
 | 
                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) { | 
 | 
                        cipherSuites = Arrays.asList(sessionSuite,  | 
 | 
                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);  | 
 | 
                    } else {     | 
 | 
                        cipherSuites = Arrays.asList(sessionSuite);  | 
 | 
                    }  | 
 | 
 | 
 | 
                    if (SSLLogger.isOn &&  | 
 | 
                            SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                        SSLLogger.finest(  | 
 | 
                            "No new session is allowed, so try to resume " +  | 
 | 
                            "the session cipher suite only", sessionSuite);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                chc.isResumption = true;  | 
 | 
                chc.resumingSession = session;  | 
 | 
            }  | 
 | 
 | 
 | 
            if (session == null) { | 
 | 
                if (!chc.sslConfig.enableSessionCreation) { | 
 | 
                    throw new SSLHandshakeException(  | 
 | 
                            "No new session is allowed and " +  | 
 | 
                            "no existing session can be resumed");  | 
 | 
                }  | 
 | 
 | 
 | 
                if (maxProtocolVersion.useTLS13PlusSpec() &&  | 
 | 
                        SSLConfiguration.useCompatibilityMode) { | 
 | 
                    // In compatibility mode, the TLS 1.3 legacy_session_id  | 
 | 
                    // field MUST be non-empty, so a client not offering a  | 
 | 
                      | 
 | 
                    sessionId =  | 
 | 
                        new SessionId(true, chc.sslContext.getSecureRandom());  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            ProtocolVersion minimumVersion = ProtocolVersion.NONE;  | 
 | 
            for (ProtocolVersion pv : chc.activeProtocols) { | 
 | 
                if (minimumVersion == ProtocolVersion.NONE ||  | 
 | 
                        pv.compare(minimumVersion) < 0) { | 
 | 
                    minimumVersion = pv;  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            if (!minimumVersion.useTLS13PlusSpec()) { | 
 | 
                if (chc.conContext.secureRenegotiation &&  | 
 | 
                        cipherSuites.contains(  | 
 | 
                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) { | 
 | 
                      | 
 | 
                    cipherSuites = new LinkedList<>(cipherSuites);  | 
 | 
                    cipherSuites.remove(  | 
 | 
                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            boolean negotiable = false;  | 
 | 
            for (CipherSuite suite : cipherSuites) { | 
 | 
                if (chc.isNegotiable(suite)) { | 
 | 
                    negotiable = true;  | 
 | 
                    break;  | 
 | 
                }  | 
 | 
            }  | 
 | 
            if (!negotiable) { | 
 | 
                throw new SSLHandshakeException("No negotiable cipher suite"); | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            ProtocolVersion clientHelloVersion = maxProtocolVersion;  | 
 | 
            if (clientHelloVersion.useTLS13PlusSpec()) { | 
 | 
                // In TLS 1.3, the client indicates its version preferences  | 
 | 
                // in the "supported_versions" extension and the client_version  | 
 | 
                  | 
 | 
                clientHelloVersion = ProtocolVersion.TLS12;  | 
 | 
            }  | 
 | 
 | 
 | 
            ClientHelloMessage chm = new ClientHelloMessage(chc,  | 
 | 
                    clientHelloVersion.id, sessionId, cipherSuites,  | 
 | 
                    chc.sslContext.getSecureRandom());  | 
 | 
 | 
 | 
              | 
 | 
            chc.clientHelloRandom = chm.clientRandom;  | 
 | 
            chc.clientHelloVersion = clientHelloVersion.id;  | 
 | 
 | 
 | 
              | 
 | 
            SSLExtension[] extTypes = chc.sslConfig.getEnabledExtensions(  | 
 | 
                    SSLHandshake.CLIENT_HELLO, chc.activeProtocols);  | 
 | 
            chm.extensions.produce(chc, extTypes);  | 
 | 
 | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                SSLLogger.fine("Produced ClientHello handshake message", chm); | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            chm.write(chc.handshakeOutput);  | 
 | 
            chc.handshakeOutput.flush();  | 
 | 
 | 
 | 
            // Reserve the initial ClientHello message for the follow on  | 
 | 
              | 
 | 
            chc.initialClientHelloMsg = chm;  | 
 | 
 | 
 | 
              | 
 | 
            chc.handshakeConsumers.put(  | 
 | 
                    SSLHandshake.SERVER_HELLO.id, SSLHandshake.SERVER_HELLO);  | 
 | 
 | 
 | 
              | 
 | 
            return null;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private static final  | 
 | 
            class ClientHelloProducer implements HandshakeProducer { | 
 | 
          | 
 | 
        private ClientHelloProducer() { | 
 | 
            // blank  | 
 | 
        }  | 
 | 
 | 
 | 
        // Response to one of the following handshake message:  | 
 | 
        //     HelloRequest                     (SSL 3.0/TLS 1.0/1.1/1.2)  | 
 | 
          | 
 | 
        @Override  | 
 | 
        public byte[] produce(ConnectionContext context,  | 
 | 
                HandshakeMessage message) throws IOException { | 
 | 
              | 
 | 
            ClientHandshakeContext chc = (ClientHandshakeContext)context;  | 
 | 
 | 
 | 
            SSLHandshake ht = message.handshakeType();  | 
 | 
            if (ht == null) { | 
 | 
                throw new UnsupportedOperationException("Not supported yet."); | 
 | 
            }  | 
 | 
 | 
 | 
            switch (ht) { | 
 | 
                case HELLO_REQUEST:  | 
 | 
                      | 
 | 
                    try { | 
 | 
                        chc.kickstart();  | 
 | 
                    } catch (IOException ioe) { | 
 | 
                        throw chc.conContext.fatal(  | 
 | 
                                Alert.HANDSHAKE_FAILURE, ioe);  | 
 | 
                    }  | 
 | 
 | 
 | 
                      | 
 | 
                    return null;  | 
 | 
                case HELLO_RETRY_REQUEST:  | 
 | 
                    // TLS 1.3  | 
 | 
                    // The HelloRetryRequest consumer should have updated the  | 
 | 
                      | 
 | 
                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                        SSLLogger.fine(  | 
 | 
                            "Produced ClientHello(HRR) handshake message",  | 
 | 
                            chc.initialClientHelloMsg);  | 
 | 
                    }  | 
 | 
 | 
 | 
                      | 
 | 
                    chc.initialClientHelloMsg.write(chc.handshakeOutput);  | 
 | 
                    chc.handshakeOutput.flush();  | 
 | 
 | 
 | 
                      | 
 | 
                    chc.conContext.consumers.putIfAbsent(  | 
 | 
                            ContentType.CHANGE_CIPHER_SPEC.id,  | 
 | 
                            ChangeCipherSpec.t13Consumer);  | 
 | 
                    chc.handshakeConsumers.put(SSLHandshake.SERVER_HELLO.id,  | 
 | 
                            SSLHandshake.SERVER_HELLO);  | 
 | 
 | 
 | 
                      | 
 | 
                    return null;  | 
 | 
                default:  | 
 | 
                    throw new UnsupportedOperationException(  | 
 | 
                            "Not supported yet.");  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private static final class ClientHelloConsumer implements SSLConsumer { | 
 | 
          | 
 | 
        private ClientHelloConsumer() { | 
 | 
            // blank  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public void consume(ConnectionContext context,  | 
 | 
                ByteBuffer message) throws IOException { | 
 | 
              | 
 | 
            ServerHandshakeContext shc = (ServerHandshakeContext)context;  | 
 | 
 | 
 | 
              | 
 | 
            shc.handshakeConsumers.remove(SSLHandshake.CLIENT_HELLO.id);  | 
 | 
            if (!shc.handshakeConsumers.isEmpty()) { | 
 | 
                throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,  | 
 | 
                        "No more handshake message allowed " +  | 
 | 
                        "in a ClientHello flight");  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            SSLExtension[] enabledExtensions =  | 
 | 
                    shc.sslConfig.getEnabledExtensions(  | 
 | 
                            SSLHandshake.CLIENT_HELLO);  | 
 | 
 | 
 | 
            ClientHelloMessage chm =  | 
 | 
                    new ClientHelloMessage(shc, message, enabledExtensions);  | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                SSLLogger.fine("Consuming ClientHello handshake message", chm); | 
 | 
            }  | 
 | 
 | 
 | 
            shc.clientHelloVersion = chm.clientVersion;  | 
 | 
            onClientHello(shc, chm);  | 
 | 
        }  | 
 | 
 | 
 | 
        private void onClientHello(ServerHandshakeContext context,  | 
 | 
                ClientHelloMessage clientHello) throws IOException { | 
 | 
            // Negotiate protocol version.  | 
 | 
            //  | 
 | 
              | 
 | 
            SSLExtension[] extTypes = new SSLExtension[] { | 
 | 
                    SSLExtension.CH_SUPPORTED_VERSIONS  | 
 | 
                };  | 
 | 
            clientHello.extensions.consumeOnLoad(context, extTypes);  | 
 | 
 | 
 | 
            ProtocolVersion negotiatedProtocol;  | 
 | 
            CHSupportedVersionsSpec svs =  | 
 | 
                    (CHSupportedVersionsSpec)context.handshakeExtensions.get(  | 
 | 
                            SSLExtension.CH_SUPPORTED_VERSIONS);  | 
 | 
            if (svs != null) { | 
 | 
                negotiatedProtocol =  | 
 | 
                        negotiateProtocol(context, svs.requestedProtocols);  | 
 | 
            } else { | 
 | 
                negotiatedProtocol =  | 
 | 
                        negotiateProtocol(context, clientHello.clientVersion);  | 
 | 
            }  | 
 | 
            context.negotiatedProtocol = negotiatedProtocol;  | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                SSLLogger.fine(  | 
 | 
                    "Negotiated protocol version: " + negotiatedProtocol.name);  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            if (negotiatedProtocol.useTLS13PlusSpec()) { | 
 | 
                t13HandshakeConsumer.consume(context, clientHello);  | 
 | 
            } else { | 
 | 
                t12HandshakeConsumer.consume(context, clientHello);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        // Select a protocol version according to the  | 
 | 
          | 
 | 
        private ProtocolVersion negotiateProtocol(  | 
 | 
                ServerHandshakeContext context,  | 
 | 
                int clientHelloVersion) throws SSLException { | 
 | 
 | 
 | 
            // Per TLS 1.3 specification, server MUST negotiate TLS 1.2 or prior  | 
 | 
              | 
 | 
            int chv = clientHelloVersion;  | 
 | 
            if (chv > ProtocolVersion.TLS12.id) { | 
 | 
                chv = ProtocolVersion.TLS12.id;  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            ProtocolVersion pv = ProtocolVersion.selectedFrom(  | 
 | 
                    context.activeProtocols, chv);  | 
 | 
            if (pv == null || pv == ProtocolVersion.NONE ||  | 
 | 
                    pv == ProtocolVersion.SSL20Hello) { | 
 | 
                throw context.conContext.fatal(Alert.PROTOCOL_VERSION,  | 
 | 
                    "Client requested protocol " +  | 
 | 
                    ProtocolVersion.nameOf(clientHelloVersion) +  | 
 | 
                    " is not enabled or supported in server context");  | 
 | 
            }  | 
 | 
 | 
 | 
            return pv;  | 
 | 
        }  | 
 | 
 | 
 | 
        // Select a protocol version according to the  | 
 | 
          | 
 | 
        private ProtocolVersion negotiateProtocol(  | 
 | 
                ServerHandshakeContext context,  | 
 | 
                int[] clientSupportedVersions) throws SSLException { | 
 | 
 | 
 | 
            // The client supported protocol versions are present in client  | 
 | 
            // preference order.  This implementation chooses to use the server  | 
 | 
              | 
 | 
            for (ProtocolVersion spv : context.activeProtocols) { | 
 | 
                if (spv == ProtocolVersion.SSL20Hello) { | 
 | 
                    continue;  | 
 | 
                }  | 
 | 
                for (int cpv : clientSupportedVersions) { | 
 | 
                    if (cpv == ProtocolVersion.SSL20Hello.id) { | 
 | 
                        continue;  | 
 | 
                    }  | 
 | 
                    if (spv.id == cpv) { | 
 | 
                        return spv;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            throw context.conContext.fatal(Alert.PROTOCOL_VERSION,  | 
 | 
                "The client supported protocol versions " + Arrays.toString(  | 
 | 
                    ProtocolVersion.toStringArray(clientSupportedVersions)) +  | 
 | 
                " are not accepted by server preferences " +  | 
 | 
                context.activeProtocols);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static final  | 
 | 
            class T12ClientHelloConsumer implements HandshakeConsumer { | 
 | 
          | 
 | 
        private T12ClientHelloConsumer() { | 
 | 
            // blank  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public void consume(ConnectionContext context,  | 
 | 
                HandshakeMessage message) throws IOException { | 
 | 
              | 
 | 
            ServerHandshakeContext shc = (ServerHandshakeContext)context;  | 
 | 
            ClientHelloMessage clientHello = (ClientHelloMessage)message;  | 
 | 
 | 
 | 
            //  | 
 | 
            // validate  | 
 | 
            //  | 
 | 
 | 
 | 
            // Reject client initiated renegotiation?  | 
 | 
            //  | 
 | 
            // If server side should reject client-initiated renegotiation,  | 
 | 
            // send an Alert.HANDSHAKE_FAILURE fatal alert, not a  | 
 | 
            // no_renegotiation warning alert (no_renegotiation must be a  | 
 | 
            // warning: RFC 2246).  no_renegotiation might seem more  | 
 | 
            // natural at first, but warnings are not appropriate because  | 
 | 
            // the sending party does not know how the receiving party  | 
 | 
            // will behave.  This state must be treated as a fatal server  | 
 | 
            // condition.  | 
 | 
            //  | 
 | 
              | 
 | 
            if (shc.conContext.isNegotiated) { | 
 | 
                if (!shc.conContext.secureRenegotiation &&  | 
 | 
                        !HandshakeContext.allowUnsafeRenegotiation) { | 
 | 
                    throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,  | 
 | 
                            "Unsafe renegotiation is not allowed");  | 
 | 
                }  | 
 | 
 | 
 | 
                if (ServerHandshakeContext.rejectClientInitiatedRenego &&  | 
 | 
                        !shc.kickstartMessageDelivered) { | 
 | 
                    throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,  | 
 | 
                            "Client initiated renegotiation is not allowed");  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            if (clientHello.sessionId.length() != 0) { | 
 | 
                SSLSessionImpl previous = ((SSLSessionContextImpl)shc.sslContext  | 
 | 
                            .engineGetServerSessionContext())  | 
 | 
                            .get(clientHello.sessionId.getId());  | 
 | 
 | 
 | 
                boolean resumingSession =  | 
 | 
                        (previous != null) && previous.isRejoinable();  | 
 | 
                if (!resumingSession) { | 
 | 
                    if (SSLLogger.isOn &&  | 
 | 
                            SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                        SSLLogger.finest(  | 
 | 
                                "Can't resume, " +  | 
 | 
                                "the existing session is not rejoinable");  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                  | 
 | 
                if (resumingSession) { | 
 | 
                    ProtocolVersion sessionProtocol =  | 
 | 
                            previous.getProtocolVersion();  | 
 | 
                    if (sessionProtocol != shc.negotiatedProtocol) { | 
 | 
                        resumingSession = false;  | 
 | 
                        if (SSLLogger.isOn &&  | 
 | 
                                SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                            SSLLogger.finest(  | 
 | 
                                "Can't resume, not the same protocol version");  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                  | 
 | 
                if (resumingSession &&  | 
 | 
                    (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) { | 
 | 
                    try { | 
 | 
                        previous.getPeerPrincipal();  | 
 | 
                    } catch (SSLPeerUnverifiedException e) { | 
 | 
                        resumingSession = false;  | 
 | 
                        if (SSLLogger.isOn &&  | 
 | 
                                SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                            SSLLogger.finest(  | 
 | 
                                "Can't resume, " +  | 
 | 
                                "client authentication is required");  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                  | 
 | 
                if (resumingSession) { | 
 | 
                    CipherSuite suite = previous.getSuite();  | 
 | 
                    if ((!shc.isNegotiable(suite)) ||  | 
 | 
                            (!clientHello.cipherSuites.contains(suite))) { | 
 | 
                        resumingSession = false;  | 
 | 
                        if (SSLLogger.isOn &&  | 
 | 
                                SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                            SSLLogger.finest(  | 
 | 
                                "Can't resume, " +  | 
 | 
                                "the session cipher suite is absent");  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                  | 
 | 
                if (resumingSession) { | 
 | 
                    CipherSuite suite = previous.getSuite();  | 
 | 
                    if (suite.keyExchange == K_KRB5 ||  | 
 | 
                            suite.keyExchange == K_KRB5_EXPORT) { | 
 | 
                        Principal localPrincipal = previous.getLocalPrincipal();  | 
 | 
 | 
 | 
                        Subject subject = null;  | 
 | 
                        try { | 
 | 
                            subject = AccessController.doPrivileged(  | 
 | 
                                    new PrivilegedExceptionAction<Subject>() { | 
 | 
                                        @Override  | 
 | 
                                        public Subject run() throws Exception { | 
 | 
                                            return Krb5Helper.getServerSubject(  | 
 | 
                                                    shc.conContext.acc);  | 
 | 
                                        }});  | 
 | 
                        } catch (PrivilegedActionException e) { | 
 | 
                            subject = null;  | 
 | 
                            if (SSLLogger.isOn &&  | 
 | 
                                    SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                                SSLLogger.finest("Attempt to obtain" + | 
 | 
                                        " subject failed!");  | 
 | 
                            }  | 
 | 
                        }  | 
 | 
 | 
 | 
                        if (subject != null) { | 
 | 
                              | 
 | 
                            if (Krb5Helper.isRelated(subject, localPrincipal)) { | 
 | 
                                if (SSLLogger.isOn &&  | 
 | 
                                        SSLLogger.isOn("ssl,handshake,verbose")) | 
 | 
                                    SSLLogger.finest("Subject can" + | 
 | 
                                            " provide creds for princ");  | 
 | 
                            } else { | 
 | 
                                resumingSession = false;  | 
 | 
                                if (SSLLogger.isOn &&  | 
 | 
                                        SSLLogger.isOn("ssl,handshake,verbose")) | 
 | 
                                    SSLLogger.finest("Subject cannot" + | 
 | 
                                            " provide creds for princ");  | 
 | 
                            }  | 
 | 
                        } else { | 
 | 
                            resumingSession = false;  | 
 | 
                            if (SSLLogger.isOn &&  | 
 | 
                                    SSLLogger.isOn("ssl,handshake,verbose")) | 
 | 
                                SSLLogger.finest("Kerberos credentials are" + | 
 | 
                                        " not present in the current Subject;" +  | 
 | 
                                        " check if " +  | 
 | 
                                        " javax.security.auth.useSubjectCredsOnly" +  | 
 | 
                                        " system property has been set to false");  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                // ensure that the endpoint identification algorithm matches the  | 
 | 
                  | 
 | 
                String identityAlg = shc.sslConfig.identificationProtocol;  | 
 | 
                if (resumingSession && identityAlg != null) { | 
 | 
                    String sessionIdentityAlg =  | 
 | 
                        previous.getIdentificationProtocol();  | 
 | 
                    if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { | 
 | 
                        if (SSLLogger.isOn &&  | 
 | 
                        SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                            SSLLogger.finest("Can't resume, endpoint id" + | 
 | 
                            " algorithm does not match, requested: " +  | 
 | 
                            identityAlg + ", cached: " + sessionIdentityAlg);  | 
 | 
                        }  | 
 | 
                        resumingSession = false;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                // So far so good.  Note that the handshake extensions may reset  | 
 | 
                  | 
 | 
                shc.isResumption = resumingSession;  | 
 | 
                shc.resumingSession = resumingSession ? previous : null;  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            shc.clientHelloRandom = clientHello.clientRandom;  | 
 | 
 | 
 | 
              | 
 | 
            SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions(  | 
 | 
                    SSLHandshake.CLIENT_HELLO);  | 
 | 
            clientHello.extensions.consumeOnLoad(shc, extTypes);  | 
 | 
 | 
 | 
            //  | 
 | 
            // update  | 
 | 
              | 
 | 
            if (!shc.conContext.isNegotiated) { | 
 | 
                shc.conContext.protocolVersion = shc.negotiatedProtocol;  | 
 | 
                shc.conContext.outputRecord.setVersion(shc.negotiatedProtocol);  | 
 | 
            }  | 
 | 
 | 
 | 
            // update the responders  | 
 | 
            //  | 
 | 
            // Only need to ServerHello, which may add more responders later.  | 
 | 
            // Note that ServerHello and HelloRetryRequest share the same  | 
 | 
            // handshake type/id.  The ServerHello producer may be replaced  | 
 | 
              | 
 | 
            shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,  | 
 | 
                    SSLHandshake.SERVER_HELLO);  | 
 | 
 | 
 | 
            //  | 
 | 
            // produce  | 
 | 
              | 
 | 
            SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] { | 
 | 
                SSLHandshake.SERVER_HELLO,  | 
 | 
 | 
 | 
                  | 
 | 
                SSLHandshake.CERTIFICATE,  | 
 | 
                SSLHandshake.CERTIFICATE_STATUS,  | 
 | 
                SSLHandshake.SERVER_KEY_EXCHANGE,  | 
 | 
                SSLHandshake.CERTIFICATE_REQUEST,  | 
 | 
                SSLHandshake.SERVER_HELLO_DONE,  | 
 | 
 | 
 | 
                  | 
 | 
                SSLHandshake.FINISHED  | 
 | 
            };  | 
 | 
 | 
 | 
            for (SSLHandshake hs : probableHandshakeMessages) { | 
 | 
                HandshakeProducer handshakeProducer =  | 
 | 
                        shc.handshakeProducers.remove(hs.id);  | 
 | 
                if (handshakeProducer != null) { | 
 | 
                    handshakeProducer.produce(context, clientHello);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private static final  | 
 | 
            class T13ClientHelloConsumer implements HandshakeConsumer { | 
 | 
          | 
 | 
        private T13ClientHelloConsumer() { | 
 | 
            // blank  | 
 | 
        }  | 
 | 
 | 
 | 
        @Override  | 
 | 
        public void consume(ConnectionContext context,  | 
 | 
                HandshakeMessage message) throws IOException { | 
 | 
              | 
 | 
            ServerHandshakeContext shc = (ServerHandshakeContext)context;  | 
 | 
            ClientHelloMessage clientHello = (ClientHelloMessage)message;  | 
 | 
 | 
 | 
            // [RFC 8446] TLS 1.3 forbids renegotiation. If a server has  | 
 | 
            // negotiated TLS 1.3 and receives a ClientHello at any other  | 
 | 
            // time, it MUST terminate the connection with an  | 
 | 
              | 
 | 
            if (shc.conContext.isNegotiated) { | 
 | 
                throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,  | 
 | 
                        "Received unexpected renegotiation handshake message");  | 
 | 
            }  | 
 | 
 | 
 | 
            // The client may send a dummy change_cipher_spec record  | 
 | 
              | 
 | 
            shc.conContext.consumers.putIfAbsent(  | 
 | 
                    ContentType.CHANGE_CIPHER_SPEC.id,  | 
 | 
                    ChangeCipherSpec.t13Consumer);  | 
 | 
 | 
 | 
            // Is it a resumption?  | 
 | 
            //  | 
 | 
            // Check and launch the "psk_key_exchange_modes" and  | 
 | 
            // "pre_shared_key" extensions first, which will reset the  | 
 | 
              | 
 | 
            shc.isResumption = true;  | 
 | 
            SSLExtension[] extTypes = new SSLExtension[] { | 
 | 
                    SSLExtension.PSK_KEY_EXCHANGE_MODES,  | 
 | 
                    SSLExtension.CH_PRE_SHARED_KEY  | 
 | 
                };  | 
 | 
            clientHello.extensions.consumeOnLoad(shc, extTypes);  | 
 | 
 | 
 | 
            // Check and launch ClientHello extensions other than  | 
 | 
            // "psk_key_exchange_modes", "pre_shared_key", "protocol_version"  | 
 | 
            // and "key_share" extensions.  | 
 | 
            //  | 
 | 
            // These extensions may discard session resumption, or ask for  | 
 | 
              | 
 | 
            extTypes = shc.sslConfig.getExclusiveExtensions(  | 
 | 
                    SSLHandshake.CLIENT_HELLO,  | 
 | 
                    Arrays.asList(  | 
 | 
                            SSLExtension.PSK_KEY_EXCHANGE_MODES,  | 
 | 
                            SSLExtension.CH_PRE_SHARED_KEY,  | 
 | 
                            SSLExtension.CH_SUPPORTED_VERSIONS));  | 
 | 
            clientHello.extensions.consumeOnLoad(shc, extTypes);  | 
 | 
 | 
 | 
            if (!shc.handshakeProducers.isEmpty()) { | 
 | 
                  | 
 | 
                goHelloRetryRequest(shc, clientHello);  | 
 | 
            } else { | 
 | 
                goServerHello(shc, clientHello);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private void goHelloRetryRequest(ServerHandshakeContext shc,  | 
 | 
                ClientHelloMessage clientHello) throws IOException { | 
 | 
            HandshakeProducer handshakeProducer =  | 
 | 
                    shc.handshakeProducers.remove(  | 
 | 
                            SSLHandshake.HELLO_RETRY_REQUEST.id);  | 
 | 
            if (handshakeProducer != null) { | 
 | 
                    handshakeProducer.produce(shc, clientHello);  | 
 | 
            } else { | 
 | 
                  | 
 | 
                throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,  | 
 | 
                    "No HelloRetryRequest producer: " + shc.handshakeProducers);  | 
 | 
            }  | 
 | 
 | 
 | 
            if (!shc.handshakeProducers.isEmpty()) { | 
 | 
                  | 
 | 
                throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,  | 
 | 
                    "unknown handshake producers: " + shc.handshakeProducers);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        private void goServerHello(ServerHandshakeContext shc,  | 
 | 
                ClientHelloMessage clientHello) throws IOException { | 
 | 
            //  | 
 | 
            // validate  | 
 | 
              | 
 | 
            shc.clientHelloRandom = clientHello.clientRandom;  | 
 | 
 | 
 | 
            //  | 
 | 
            // update  | 
 | 
              | 
 | 
            if (!shc.conContext.isNegotiated) { | 
 | 
                shc.conContext.protocolVersion = shc.negotiatedProtocol;  | 
 | 
                shc.conContext.outputRecord.setVersion(shc.negotiatedProtocol);  | 
 | 
            }  | 
 | 
 | 
 | 
            // update the responders  | 
 | 
            //  | 
 | 
            // Only ServerHello/HelloRetryRequest producer, which adds  | 
 | 
              | 
 | 
            shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,  | 
 | 
                SSLHandshake.SERVER_HELLO);  | 
 | 
 | 
 | 
            SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] { | 
 | 
                SSLHandshake.SERVER_HELLO,  | 
 | 
 | 
 | 
                  | 
 | 
                SSLHandshake.ENCRYPTED_EXTENSIONS,  | 
 | 
                SSLHandshake.CERTIFICATE_REQUEST,  | 
 | 
                SSLHandshake.CERTIFICATE,  | 
 | 
                SSLHandshake.CERTIFICATE_VERIFY,  | 
 | 
                SSLHandshake.FINISHED  | 
 | 
            };  | 
 | 
 | 
 | 
            //  | 
 | 
            // produce  | 
 | 
              | 
 | 
            for (SSLHandshake hs : probableHandshakeMessages) { | 
 | 
                HandshakeProducer handshakeProducer =  | 
 | 
                        shc.handshakeProducers.remove(hs.id);  | 
 | 
                if (handshakeProducer != null) { | 
 | 
                    handshakeProducer.produce(shc, clientHello);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
}  |