| /* | |
| * Copyright (c) 2015, 2025, 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.nio.ByteBuffer; | |
| import java.security.AlgorithmConstraints; | |
| import java.security.CryptoPrimitive; | |
| import java.security.GeneralSecurityException; | |
| import java.text.MessageFormat; | |
| import java.util.*; | |
| import javax.crypto.KDF; | |
| import javax.crypto.SecretKey; | |
| import javax.crypto.spec.HKDFParameterSpec; | |
| import javax.crypto.spec.IvParameterSpec; | |
| import javax.net.ssl.SSLException; | |
| import javax.net.ssl.SSLHandshakeException; | |
| import javax.net.ssl.SSLProtocolException; | |
| import sun.security.ssl.CipherSuite.KeyExchange; | |
| import sun.security.ssl.ClientHello.ClientHelloMessage; | |
| import sun.security.ssl.SSLCipher.SSLReadCipher; | |
| import sun.security.ssl.SSLCipher.SSLWriteCipher; | |
| import sun.security.ssl.SSLHandshake.HandshakeMessage; | |
| import sun.security.ssl.SupportedVersionsExtension.SHSupportedVersionsSpec; | |
| /** | |
| * Pack of the ServerHello/HelloRetryRequest handshake message. | |
| */ | |
| final class ServerHello { | |
| static final SSLConsumer handshakeConsumer = | |
| new ServerHelloConsumer(); | |
| static final HandshakeProducer t12HandshakeProducer = | |
| new T12ServerHelloProducer(); | |
| static final HandshakeProducer t13HandshakeProducer = | |
| new T13ServerHelloProducer(); | |
| static final HandshakeProducer hrrHandshakeProducer = | |
| new T13HelloRetryRequestProducer(); | |
| static final HandshakeProducer hrrReproducer = | |
| new T13HelloRetryRequestReproducer(); | |
| private static final HandshakeConsumer t12HandshakeConsumer = | |
| new T12ServerHelloConsumer(); | |
| private static final HandshakeConsumer t13HandshakeConsumer = | |
| new T13ServerHelloConsumer(); | |
| private static final HandshakeConsumer d12HandshakeConsumer = | |
| new T12ServerHelloConsumer(); | |
| private static final HandshakeConsumer d13HandshakeConsumer = | |
| new T13ServerHelloConsumer(); | |
| private static final HandshakeConsumer t13HrrHandshakeConsumer = | |
| new T13HelloRetryRequestConsumer(); | |
| private static final HandshakeConsumer d13HrrHandshakeConsumer = | |
| new T13HelloRetryRequestConsumer(); | |
| /** | |
| * The ServerHello handshake message. | |
| */ | |
| static final class ServerHelloMessage extends HandshakeMessage { | |
| final ProtocolVersion serverVersion; // TLS 1.3 legacy | |
| final RandomCookie serverRandom; | |
| final SessionId sessionId; // TLS 1.3 legacy | |
| final CipherSuite cipherSuite; | |
| final byte compressionMethod; // TLS 1.3 legacy | |
| final SSLExtensions extensions; | |
| // The HelloRetryRequest producer needs to use the ClientHello message | |
| // for cookie generation. Please don't use this field for other | |
| // purpose unless it is really necessary. | |
| final ClientHelloMessage clientHello; | |
| // Reserved for HelloRetryRequest consumer. Please don't use this | |
| // field for other purpose unless it is really necessary. | |
| final ByteBuffer handshakeRecord; | |
| ServerHelloMessage(HandshakeContext context, | |
| ProtocolVersion serverVersion, SessionId sessionId, | |
| CipherSuite cipherSuite, RandomCookie serverRandom, | |
| ClientHelloMessage clientHello) { | |
| super(context); | |
| this.serverVersion = serverVersion; | |
| this.serverRandom = serverRandom; | |
| this.sessionId = sessionId; | |
| this.cipherSuite = cipherSuite; | |
| this.compressionMethod = 0x00; // Don't support compression. | |
| this.extensions = new SSLExtensions(this); | |
| // Reserve the ClientHello message for cookie generation. | |
| this.clientHello = clientHello; | |
| // The handshakeRecord field is used for HelloRetryRequest consumer | |
| // only. It's fine to set it to null for generating side of the | |
| // ServerHello/HelloRetryRequest message. | |
| this.handshakeRecord = null; | |
| } | |
| ServerHelloMessage(HandshakeContext context, | |
| ByteBuffer m) throws IOException { | |
| super(context); | |
| // Reserve for HelloRetryRequest consumer if needed. | |
| this.handshakeRecord = m.duplicate(); | |
| byte major = m.get(); | |
| byte minor = m.get(); | |
| this.serverVersion = ProtocolVersion.valueOf(major, minor); | |
| if (this.serverVersion == null) { | |
| // The client should only request for known protocol versions. | |
| throw context.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "Unsupported protocol version: " + | |
| ProtocolVersion.nameOf(major, minor)); | |
| } | |
| this.serverRandom = new RandomCookie(m); | |
| this.sessionId = new SessionId(Record.getBytes8(m)); | |
| try { | |
| sessionId.checkLength(serverVersion.id); | |
| } catch (SSLProtocolException ex) { | |
| throw handshakeContext.conContext.fatal( | |
| Alert.ILLEGAL_PARAMETER, ex); | |
| } | |
| int cipherSuiteId = Record.getInt16(m); | |
| this.cipherSuite = CipherSuite.valueOf(cipherSuiteId); | |
| if (cipherSuite == null || !context.isNegotiable(cipherSuite)) { | |
| throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "Server selected improper ciphersuite " + | |
| CipherSuite.nameOf(cipherSuiteId)); | |
| } | |
| this.compressionMethod = m.get(); | |
| if (compressionMethod != 0) { | |
| throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "compression type not supported, " + compressionMethod); | |
| } | |
| SSLExtension[] supportedExtensions; | |
| if (serverRandom.isHelloRetryRequest()) { | |
| supportedExtensions = context.sslConfig.getEnabledExtensions( | |
| SSLHandshake.HELLO_RETRY_REQUEST); | |
| } else { | |
| supportedExtensions = context.sslConfig.getEnabledExtensions( | |
| SSLHandshake.SERVER_HELLO); | |
| } | |
| if (m.hasRemaining()) { | |
| this.extensions = | |
| new SSLExtensions(this, m, supportedExtensions); | |
| } else { | |
| this.extensions = new SSLExtensions(this); | |
| } | |
| // The clientHello field is used for HelloRetryRequest producer | |
| // only. It's fine to set it to null for receiving side of | |
| // ServerHello/HelloRetryRequest message. | |
| this.clientHello = null; // not used, let it be null; | |
| } | |
| @Override | |
| public SSLHandshake handshakeType() { | |
| return serverRandom.isHelloRetryRequest() ? | |
| SSLHandshake.HELLO_RETRY_REQUEST : SSLHandshake.SERVER_HELLO; | |
| } | |
| @Override | |
| public int messageLength() { | |
| // almost fixed header size, except session ID and extensions: | |
| // major + minor = 2 | |
| // random = 32 | |
| // session ID len field = 1 | |
| // cipher suite = 2 | |
| // compression = 1 | |
| // extensions: if present, 2 + length of extensions | |
| // In TLS 1.3, use of certain extensions is mandatory. | |
| return 38 + sessionId.length() + extensions.length(); | |
| } | |
| @Override | |
| public void send(HandshakeOutStream hos) throws IOException { | |
| hos.putInt8(serverVersion.major); | |
| hos.putInt8(serverVersion.minor); | |
| hos.write(serverRandom.randomBytes); | |
| hos.putBytes8(sessionId.getId()); | |
| hos.putInt8((cipherSuite.id >> 8) & 0xFF); | |
| hos.putInt8(cipherSuite.id & 0xff); | |
| hos.putInt8(compressionMethod); | |
| extensions.send(hos); // In TLS 1.3, use of certain | |
| // extensions are mandatory. | |
| } | |
| @Override | |
| public String toString() { | |
| MessageFormat messageFormat = new MessageFormat( | |
| """ | |
| "{0}": '{' | |
| "server version" : "{1}", | |
| "random" : "{2}", | |
| "session id" : "{3}", | |
| "cipher suite" : "{4}", | |
| "compression methods" : "{5}", | |
| "extensions" : [ | |
| {6} | |
| ] | |
| '}'""", | |
| Locale.ENGLISH); | |
| Object[] messageFields = { | |
| serverRandom.isHelloRetryRequest() ? | |
| "HelloRetryRequest" : "ServerHello", | |
| serverVersion.name, | |
| Utilities.toHexString(serverRandom.randomBytes), | |
| sessionId.toString(), | |
| cipherSuite.name + "(" + Utilities.byte16HexString(cipherSuite.id) + ")", | |
| HexFormat.of().toHexDigits(compressionMethod), | |
| Utilities.indent(extensions.toString(), " ") | |
| }; | |
| return messageFormat.format(messageFields); | |
| } | |
| } | |
| /** | |
| * The "ServerHello" handshake message producer. | |
| */ | |
| private static final class T12ServerHelloProducer | |
| implements HandshakeProducer { | |
| // Prevent instantiation of this class. | |
| private T12ServerHelloProducer() { | |
| // blank | |
| } | |
| @Override | |
| public byte[] produce(ConnectionContext context, | |
| HandshakeMessage message) throws IOException { | |
| // The producing happens in server side only. | |
| ServerHandshakeContext shc = (ServerHandshakeContext)context; | |
| ClientHelloMessage clientHello = (ClientHelloMessage)message; | |
| // If client hasn't specified a session we can resume, start a | |
| // new one and choose its cipher suite and compression options, | |
| // unless new session creation is disabled for this connection! | |
| if (!shc.isResumption || shc.resumingSession == null) { | |
| if (!shc.sslConfig.enableSessionCreation) { | |
| throw new SSLException( | |
| "Not resumption, and no new session is allowed"); | |
| } | |
| SSLSessionImpl session = | |
| new SSLSessionImpl(shc, CipherSuite.C_NULL); | |
| session.setMaximumPacketSize(shc.sslConfig.maximumPacketSize); | |
| shc.handshakeSession = session; | |
| // consider the handshake extension impact | |
| SSLExtension[] enabledExtensions = | |
| shc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.CLIENT_HELLO, shc.negotiatedProtocol); | |
| clientHello.extensions.consumeOnTrade(shc, enabledExtensions); | |
| // negotiate the cipher suite. | |
| KeyExchangeProperties credentials = | |
| chooseCipherSuite(shc, clientHello); | |
| if (credentials == null) { | |
| throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "no cipher suites in common"); | |
| } | |
| shc.negotiatedCipherSuite = credentials.cipherSuite; | |
| shc.handshakeKeyExchange = credentials.keyExchange; | |
| shc.handshakeSession.setSuite(credentials.cipherSuite); | |
| shc.handshakePossessions.addAll( | |
| Arrays.asList(credentials.possessions)); | |
| shc.handshakeHash.determine( | |
| shc.negotiatedProtocol, shc.negotiatedCipherSuite); | |
| // Check the incoming OCSP stapling extensions and attempt | |
| // to get responses. If the resulting stapleParams is non-null, | |
| // it implies that stapling is enabled on the server side. | |
| shc.stapleParams = StatusResponseManager.processStapling(shc); | |
| shc.staplingActive = (shc.stapleParams != null); | |
| // update the responders | |
| SSLKeyExchange ke = credentials.keyExchange; | |
| if (ke != null) { | |
| for (Map.Entry<Byte, HandshakeProducer> me : | |
| ke.getHandshakeProducers(shc)) { | |
| shc.handshakeProducers.put( | |
| me.getKey(), me.getValue()); | |
| } | |
| } | |
| if ((ke != null) && | |
| (shc.sslConfig.clientAuthType != | |
| ClientAuthType.CLIENT_AUTH_NONE) && | |
| !shc.negotiatedCipherSuite.isAnonymous()) { | |
| for (SSLHandshake hs : | |
| ke.getRelatedHandshakers(shc)) { | |
| if (hs == SSLHandshake.CERTIFICATE) { | |
| shc.handshakeProducers.put( | |
| SSLHandshake.CERTIFICATE_REQUEST.id, | |
| SSLHandshake.CERTIFICATE_REQUEST); | |
| break; | |
| } | |
| } | |
| } | |
| shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO_DONE.id, | |
| SSLHandshake.SERVER_HELLO_DONE); | |
| } else { | |
| // stateless and use the client session id (RFC 5077 3.4) | |
| if (shc.statelessResumption) { | |
| shc.resumingSession = new SSLSessionImpl(shc.resumingSession, | |
| (clientHello.sessionId.length() == 0) ? | |
| new SessionId(true, | |
| shc.sslContext.getSecureRandom()) : | |
| new SessionId(clientHello.sessionId.getId()) | |
| ); | |
| } | |
| shc.handshakeSession = shc.resumingSession; | |
| shc.negotiatedProtocol = | |
| shc.resumingSession.getProtocolVersion(); | |
| shc.negotiatedCipherSuite = shc.resumingSession.getSuite(); | |
| shc.handshakeHash.determine( | |
| shc.negotiatedProtocol, shc.negotiatedCipherSuite); | |
| } | |
| // Generate the ServerHello handshake message. | |
| ServerHelloMessage shm = new ServerHelloMessage(shc, | |
| shc.negotiatedProtocol, | |
| shc.handshakeSession.getSessionId(), | |
| shc.negotiatedCipherSuite, | |
| new RandomCookie(shc), | |
| clientHello); | |
| shc.serverHelloRandom = shm.serverRandom; | |
| shc.handshakeSession.setRandoms(shc.clientHelloRandom, | |
| shc.serverHelloRandom); | |
| // Produce extensions for ServerHello handshake message. | |
| SSLExtension[] serverHelloExtensions = | |
| shc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.SERVER_HELLO, shc.negotiatedProtocol); | |
| shm.extensions.produce(shc, serverHelloExtensions); | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine("Produced ServerHello handshake message", shm); | |
| } | |
| // Output the handshake message. | |
| shm.write(shc.handshakeOutput); | |
| shc.handshakeOutput.flush(); | |
| if (shc.isResumption && shc.resumingSession != null) { | |
| SSLTrafficKeyDerivation kdg = | |
| SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); | |
| if (kdg == null) { | |
| // unlikely | |
| throw shc.conContext.fatal(Alert.INTERNAL_ERROR, | |
| "Not supported key derivation: " + | |
| shc.negotiatedProtocol); | |
| } else { | |
| shc.handshakeKeyDerivation = kdg.createKeyDerivation( | |
| shc, shc.resumingSession.getMasterSecret()); | |
| } | |
| // update the responders | |
| shc.handshakeProducers.put(SSLHandshake.FINISHED.id, | |
| SSLHandshake.FINISHED); | |
| } | |
| // The handshake message has been delivered. | |
| return null; | |
| } | |
| private static KeyExchangeProperties chooseCipherSuite( | |
| ServerHandshakeContext shc, | |
| ClientHelloMessage clientHello) throws IOException { | |
| List<CipherSuite> preferred; | |
| List<CipherSuite> proposed; | |
| if (shc.sslConfig.preferLocalCipherSuites) { | |
| preferred = shc.activeCipherSuites; | |
| proposed = clientHello.cipherSuites; | |
| } else { | |
| preferred = clientHello.cipherSuites; | |
| proposed = shc.activeCipherSuites; | |
| } | |
| List<CipherSuite> legacySuites = new LinkedList<>(); | |
| for (CipherSuite cs : preferred) { | |
| if (!HandshakeContext.isNegotiable( | |
| proposed, shc.negotiatedProtocol, cs)) { | |
| continue; | |
| } | |
| if (shc.sslConfig.clientAuthType == | |
| ClientAuthType.CLIENT_AUTH_REQUIRED) { | |
| if ((cs.keyExchange == KeyExchange.K_DH_ANON) || | |
| (cs.keyExchange == KeyExchange.K_ECDH_ANON)) { | |
| continue; | |
| } | |
| } | |
| SSLKeyExchange ke = SSLKeyExchange.valueOf( | |
| cs.keyExchange, shc.negotiatedProtocol); | |
| if (ke == null) { | |
| continue; | |
| } | |
| if (!ServerHandshakeContext.legacyAlgorithmConstraints.permits( | |
| EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), cs.name, null)) { | |
| legacySuites.add(cs); | |
| continue; | |
| } | |
| SSLPossession[] hcds = ke.createPossessions(shc); | |
| if ((hcds == null) || (hcds.length == 0)) { | |
| continue; | |
| } | |
| // The cipher suite has been negotiated. | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine("use cipher suite " + cs.name); | |
| } | |
| return new KeyExchangeProperties(cs, ke, hcds); | |
| } | |
| for (CipherSuite cs : legacySuites) { | |
| SSLKeyExchange ke = SSLKeyExchange.valueOf( | |
| cs.keyExchange, shc.negotiatedProtocol); | |
| if (ke != null) { | |
| SSLPossession[] hcds = ke.createPossessions(shc); | |
| if ((hcds != null) && (hcds.length != 0)) { | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.warning( | |
| "use legacy cipher suite " + cs.name); | |
| } | |
| return new KeyExchangeProperties(cs, ke, hcds); | |
| } | |
| } | |
| } | |
| throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "no cipher suites in common"); | |
| } | |
| private static final class KeyExchangeProperties { | |
| final CipherSuite cipherSuite; | |
| final SSLKeyExchange keyExchange; | |
| final SSLPossession[] possessions; | |
| private KeyExchangeProperties(CipherSuite cipherSuite, | |
| SSLKeyExchange keyExchange, SSLPossession[] possessions) { | |
| this.cipherSuite = cipherSuite; | |
| this.keyExchange = keyExchange; | |
| this.possessions = possessions; | |
| } | |
| } | |
| } | |
| /** | |
| * The "ServerHello" handshake message producer. | |
| */ | |
| private static final | |
| class T13ServerHelloProducer implements HandshakeProducer { | |
| // Prevent instantiation of this class. | |
| private T13ServerHelloProducer() { | |
| // blank | |
| } | |
| @Override | |
| public byte[] produce(ConnectionContext context, | |
| HandshakeMessage message) throws IOException { | |
| // The producing happens in server side only. | |
| ServerHandshakeContext shc = (ServerHandshakeContext)context; | |
| ClientHelloMessage clientHello = (ClientHelloMessage)message; | |
| SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) | |
| shc.sslContext.engineGetServerSessionContext(); | |
| // If client hasn't specified a session we can resume, start a | |
| // new one and choose its cipher suite and compression options, | |
| // unless new session creation is disabled for this connection! | |
| if (!shc.isResumption || shc.resumingSession == null) { | |
| if (!shc.sslConfig.enableSessionCreation) { | |
| throw new SSLException( | |
| "Not resumption, and no new session is allowed"); | |
| } | |
| SSLSessionImpl session = | |
| new SSLSessionImpl(shc, CipherSuite.C_NULL); | |
| session.setMaximumPacketSize(shc.sslConfig.maximumPacketSize); | |
| shc.handshakeSession = session; | |
| // consider the handshake extension impact | |
| SSLExtension[] enabledExtensions = | |
| shc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.CLIENT_HELLO, shc.negotiatedProtocol); | |
| clientHello.extensions.consumeOnTrade(shc, enabledExtensions); | |
| // negotiate the cipher suite. | |
| CipherSuite cipherSuite = chooseCipherSuite(shc, clientHello); | |
| if (cipherSuite == null) { | |
| throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "no cipher suites in common"); | |
| } | |
| shc.negotiatedCipherSuite = cipherSuite; | |
| shc.handshakeSession.setSuite(cipherSuite); | |
| shc.handshakeHash.determine( | |
| shc.negotiatedProtocol, shc.negotiatedCipherSuite); | |
| } else { | |
| shc.handshakeSession = shc.resumingSession; | |
| // consider the handshake extension impact | |
| SSLExtension[] enabledExtensions = | |
| shc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.CLIENT_HELLO, shc.negotiatedProtocol); | |
| clientHello.extensions.consumeOnTrade(shc, enabledExtensions); | |
| shc.negotiatedProtocol = | |
| shc.resumingSession.getProtocolVersion(); | |
| shc.negotiatedCipherSuite = shc.resumingSession.getSuite(); | |
| shc.handshakeHash.determine( | |
| shc.negotiatedProtocol, shc.negotiatedCipherSuite); | |
| setUpPskKD(shc, | |
| shc.resumingSession.consumePreSharedKey()); | |
| } | |
| // update the responders | |
| shc.handshakeProducers.put(SSLHandshake.ENCRYPTED_EXTENSIONS.id, | |
| SSLHandshake.ENCRYPTED_EXTENSIONS); | |
| shc.handshakeProducers.put(SSLHandshake.FINISHED.id, | |
| SSLHandshake.FINISHED); | |
| // Generate the ServerHello handshake message. | |
| ServerHelloMessage shm = new ServerHelloMessage(shc, | |
| ProtocolVersion.TLS12, // use legacy version | |
| clientHello.sessionId, // echo back | |
| shc.negotiatedCipherSuite, | |
| new RandomCookie(shc), | |
| clientHello); | |
| shc.serverHelloRandom = shm.serverRandom; | |
| // Produce extensions for ServerHello handshake message. | |
| SSLExtension[] serverHelloExtensions = | |
| shc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.SERVER_HELLO, shc.negotiatedProtocol); | |
| shm.extensions.produce(shc, serverHelloExtensions); | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine("Produced ServerHello handshake message", shm); | |
| } | |
| // Output the handshake message. | |
| shm.write(shc.handshakeOutput); | |
| shc.handshakeOutput.flush(); | |
| // Change client/server handshake traffic secrets. | |
| // Refresh handshake hash | |
| shc.handshakeHash.update(); | |
| // Change client/server handshake traffic secrets. | |
| SSLKeyExchange ke = shc.handshakeKeyExchange; | |
| if (ke == null) { | |
| // unlikely | |
| throw shc.conContext.fatal(Alert.INTERNAL_ERROR, | |
| "Not negotiated key shares"); | |
| } | |
| SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc); | |
| SecretKey handshakeSecret = handshakeKD.deriveKey( | |
| "TlsHandshakeSecret"); | |
| SSLTrafficKeyDerivation kdg = | |
| SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); | |
| if (kdg == null) { | |
| // unlikely | |
| throw shc.conContext.fatal(Alert.INTERNAL_ERROR, | |
| "Not supported key derivation: " + | |
| shc.negotiatedProtocol); | |
| } | |
| SSLKeyDerivation kd = | |
| new SSLSecretDerivation(shc, handshakeSecret); | |
| // update the handshake traffic read keys. | |
| SecretKey readSecret = kd.deriveKey( | |
| "TlsClientHandshakeTrafficSecret"); | |
| SSLKeyDerivation readKD = | |
| kdg.createKeyDerivation(shc, readSecret); | |
| SecretKey readKey = readKD.deriveKey("TlsKey"); | |
| IvParameterSpec readIv = | |
| new IvParameterSpec(readKD.deriveData("TlsIv")); | |
| SSLReadCipher readCipher; | |
| try { | |
| readCipher = | |
| shc.negotiatedCipherSuite.bulkCipher.createReadCipher( | |
| Authenticator.valueOf(shc.negotiatedProtocol), | |
| shc.negotiatedProtocol, readKey, readIv, | |
| shc.sslContext.getSecureRandom()); | |
| } catch (GeneralSecurityException gse) { | |
| // unlikely | |
| throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "Missing cipher algorithm", gse); | |
| } | |
| if (readCipher == null) { | |
| throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "Illegal cipher suite (" + shc.negotiatedCipherSuite + | |
| ") and protocol version (" + shc.negotiatedProtocol + | |
| ")"); | |
| } | |
| shc.baseReadSecret = readSecret; | |
| shc.conContext.inputRecord.changeReadCiphers(readCipher); | |
| // update the handshake traffic write secret. | |
| SecretKey writeSecret = kd.deriveKey( | |
| "TlsServerHandshakeTrafficSecret"); | |
| SSLKeyDerivation writeKD = | |
| kdg.createKeyDerivation(shc, writeSecret); | |
| SecretKey writeKey = writeKD.deriveKey("TlsKey"); | |
| IvParameterSpec writeIv = | |
| new IvParameterSpec(writeKD.deriveData("TlsIv")); | |
| SSLWriteCipher writeCipher; | |
| try { | |
| writeCipher = | |
| shc.negotiatedCipherSuite.bulkCipher.createWriteCipher( | |
| Authenticator.valueOf(shc.negotiatedProtocol), | |
| shc.negotiatedProtocol, writeKey, writeIv, | |
| shc.sslContext.getSecureRandom()); | |
| } catch (GeneralSecurityException gse) { | |
| // unlikely | |
| throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "Missing cipher algorithm", gse); | |
| } | |
| if (writeCipher == null) { | |
| throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "Illegal cipher suite (" + shc.negotiatedCipherSuite + | |
| ") and protocol version (" + shc.negotiatedProtocol + | |
| ")"); | |
| } | |
| shc.baseWriteSecret = writeSecret; | |
| shc.conContext.outputRecord.changeWriteCiphers( | |
| writeCipher, (clientHello.sessionId.length() != 0)); | |
| // Update the context for master key derivation. | |
| shc.handshakeKeyDerivation = kd; | |
| // Check if the server supports stateless resumption | |
| if (sessionCache.statelessEnabled()) { | |
| shc.statelessResumption = true; | |
| } | |
| // The handshake message has been delivered. | |
| return null; | |
| } | |
| private static CipherSuite chooseCipherSuite( | |
| ServerHandshakeContext shc, | |
| ClientHelloMessage clientHello) { | |
| List<CipherSuite> preferred; | |
| List<CipherSuite> proposed; | |
| if (shc.sslConfig.preferLocalCipherSuites) { | |
| preferred = shc.activeCipherSuites; | |
| proposed = clientHello.cipherSuites; | |
| } else { | |
| preferred = clientHello.cipherSuites; | |
| proposed = shc.activeCipherSuites; | |
| } | |
| CipherSuite legacySuite = null; | |
| AlgorithmConstraints legacyConstraints = | |
| ServerHandshakeContext.legacyAlgorithmConstraints; | |
| for (CipherSuite cs : preferred) { | |
| if (!HandshakeContext.isNegotiable( | |
| proposed, shc.negotiatedProtocol, cs)) { | |
| continue; | |
| } | |
| if ((legacySuite == null) && | |
| !legacyConstraints.permits( | |
| EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), | |
| cs.name, null)) { | |
| legacySuite = cs; | |
| continue; | |
| } | |
| // The cipher suite has been negotiated. | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine("use cipher suite " + cs.name); | |
| } | |
| return cs; | |
| } | |
| if (legacySuite != null) { | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.warning( | |
| "use legacy cipher suite " + legacySuite.name); | |
| } | |
| return legacySuite; | |
| } | |
| // no cipher suites in common | |
| return null; | |
| } | |
| } | |
| /** | |
| * The "HelloRetryRequest" handshake message producer. | |
| */ | |
| private static final | |
| class T13HelloRetryRequestProducer implements HandshakeProducer { | |
| // Prevent instantiation of this class. | |
| private T13HelloRetryRequestProducer() { | |
| // blank | |
| } | |
| @Override | |
| public byte[] produce(ConnectionContext context, | |
| HandshakeMessage message) throws IOException { | |
| ServerHandshakeContext shc = (ServerHandshakeContext) context; | |
| ClientHelloMessage clientHello = (ClientHelloMessage) message; | |
| // negotiate the cipher suite. | |
| CipherSuite cipherSuite = | |
| T13ServerHelloProducer.chooseCipherSuite(shc, clientHello); | |
| if (cipherSuite == null) { | |
| throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "no cipher suites in common for hello retry request"); | |
| } | |
| ServerHelloMessage hhrm = new ServerHelloMessage(shc, | |
| ProtocolVersion.TLS12, // use legacy version | |
| clientHello.sessionId, // echo back | |
| cipherSuite, | |
| RandomCookie.hrrRandom, | |
| clientHello | |
| ); | |
| shc.negotiatedCipherSuite = cipherSuite; | |
| shc.handshakeHash.determine( | |
| shc.negotiatedProtocol, shc.negotiatedCipherSuite); | |
| // Produce extensions for HelloRetryRequest handshake message. | |
| SSLExtension[] serverHelloExtensions = | |
| shc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.HELLO_RETRY_REQUEST, shc.negotiatedProtocol); | |
| hhrm.extensions.produce(shc, serverHelloExtensions); | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine( | |
| "Produced HelloRetryRequest handshake message", hhrm); | |
| } | |
| // Output the handshake message. | |
| hhrm.write(shc.handshakeOutput); | |
| shc.handshakeOutput.flush(); | |
| // In TLS1.3 middlebox compatibility mode the server sends a | |
| // dummy change_cipher_spec record immediately after its | |
| // first handshake message. This may either be after | |
| // a ServerHello or a HelloRetryRequest. | |
| // (RFC 8446, Appendix D.4) | |
| shc.conContext.outputRecord.changeWriteCiphers( | |
| SSLWriteCipher.nullTlsWriteCipher(), | |
| (clientHello.sessionId.length() != 0)); | |
| // Stateless, shall we clean up the handshake context as well? | |
| shc.handshakeHash.finish(); // forgot about the handshake hash | |
| shc.handshakeExtensions.clear(); | |
| // What's the expected response? | |
| shc.handshakeConsumers.put( | |
| SSLHandshake.CLIENT_HELLO.id, SSLHandshake.CLIENT_HELLO); | |
| // The handshake message has been delivered. | |
| return null; | |
| } | |
| } | |
| /** | |
| * The "HelloRetryRequest" handshake message reproducer. | |
| */ | |
| private static final | |
| class T13HelloRetryRequestReproducer implements HandshakeProducer { | |
| // Prevent instantiation of this class. | |
| private T13HelloRetryRequestReproducer() { | |
| // blank | |
| } | |
| @Override | |
| public byte[] produce(ConnectionContext context, | |
| HandshakeMessage message) throws IOException { | |
| ServerHandshakeContext shc = (ServerHandshakeContext) context; | |
| ClientHelloMessage clientHello = (ClientHelloMessage) message; | |
| // negotiate the cipher suite. | |
| CipherSuite cipherSuite = shc.negotiatedCipherSuite; | |
| ServerHelloMessage hhrm = new ServerHelloMessage(shc, | |
| ProtocolVersion.TLS12, // use legacy version | |
| clientHello.sessionId, // echo back | |
| cipherSuite, | |
| RandomCookie.hrrRandom, | |
| clientHello | |
| ); | |
| // Produce extensions for HelloRetryRequest handshake message. | |
| SSLExtension[] serverHelloExtensions = | |
| shc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.MESSAGE_HASH, shc.negotiatedProtocol); | |
| hhrm.extensions.produce(shc, serverHelloExtensions); | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine( | |
| "Reproduced HelloRetryRequest handshake message", hhrm); | |
| } | |
| HandshakeOutStream hos = new HandshakeOutStream(null); | |
| hhrm.write(hos); | |
| return hos.toByteArray(); | |
| } | |
| } | |
| /** | |
| * The "ServerHello" handshake message consumer. | |
| */ | |
| private static final | |
| class ServerHelloConsumer implements SSLConsumer { | |
| // Prevent instantiation of this class. | |
| private ServerHelloConsumer() { | |
| // blank | |
| } | |
| @Override | |
| public void consume(ConnectionContext context, | |
| ByteBuffer message) throws IOException { | |
| // The consuming happens in client side only. | |
| ClientHandshakeContext chc = (ClientHandshakeContext)context; | |
| // clean up this consumer | |
| chc.handshakeConsumers.remove(SSLHandshake.SERVER_HELLO.id); | |
| if (!chc.handshakeConsumers.isEmpty()) { | |
| // DTLS 1.0/1.2 | |
| chc.handshakeConsumers.remove( | |
| SSLHandshake.HELLO_VERIFY_REQUEST.id); | |
| } | |
| if (!chc.handshakeConsumers.isEmpty()) { | |
| throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, | |
| "No more message expected before ServerHello is processed"); | |
| } | |
| ServerHelloMessage shm = new ServerHelloMessage(chc, message); | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine("Consuming ServerHello handshake message", shm); | |
| } | |
| if (shm.serverRandom.isHelloRetryRequest()) { | |
| onHelloRetryRequest(chc, shm); | |
| } else { | |
| onServerHello(chc, shm); | |
| } | |
| } | |
| private void onHelloRetryRequest(ClientHandshakeContext chc, | |
| ServerHelloMessage helloRetryRequest) throws IOException { | |
| // Negotiate protocol version. | |
| // | |
| // Check and launch SupportedVersions. | |
| SSLExtension[] extTypes = new SSLExtension[] { | |
| SSLExtension.HRR_SUPPORTED_VERSIONS | |
| }; | |
| helloRetryRequest.extensions.consumeOnLoad(chc, extTypes); | |
| ProtocolVersion serverVersion; | |
| SHSupportedVersionsSpec svs = | |
| (SHSupportedVersionsSpec)chc.handshakeExtensions.get( | |
| SSLExtension.HRR_SUPPORTED_VERSIONS); | |
| if (svs != null) { | |
| serverVersion = // could be null | |
| ProtocolVersion.valueOf(svs.selectedVersion); | |
| } else { | |
| serverVersion = helloRetryRequest.serverVersion; | |
| } | |
| if (!chc.activeProtocols.contains(serverVersion)) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "The server selected protocol version " + serverVersion + | |
| " is not accepted by client preferences " + | |
| chc.activeProtocols); | |
| } | |
| if (!serverVersion.useTLS13PlusSpec()) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "Unexpected HelloRetryRequest for " + serverVersion.name); | |
| } | |
| chc.negotiatedProtocol = serverVersion; | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine( | |
| "Negotiated protocol version: " + serverVersion.name); | |
| } | |
| // Protocol version is negotiated, update locally supported | |
| // signature schemes according to the protocol being used. | |
| SignatureScheme.updateHandshakeLocalSupportedAlgs(chc); | |
| // TLS 1.3 key share extension may have produced client | |
| // possessions for TLS 1.3 key exchanges. | |
| // | |
| // Clean up before producing new client key share possessions. | |
| chc.handshakePossessions.clear(); | |
| if (serverVersion.isDTLS) { | |
| d13HrrHandshakeConsumer.consume(chc, helloRetryRequest); | |
| } else { | |
| t13HrrHandshakeConsumer.consume(chc, helloRetryRequest); | |
| } | |
| } | |
| private void onServerHello(ClientHandshakeContext chc, | |
| ServerHelloMessage serverHello) throws IOException { | |
| // Negotiate protocol version. | |
| // | |
| // Check and launch SupportedVersions. | |
| SSLExtension[] extTypes = new SSLExtension[] { | |
| SSLExtension.SH_SUPPORTED_VERSIONS | |
| }; | |
| serverHello.extensions.consumeOnLoad(chc, extTypes); | |
| ProtocolVersion serverVersion; | |
| SHSupportedVersionsSpec svs = | |
| (SHSupportedVersionsSpec)chc.handshakeExtensions.get( | |
| SSLExtension.SH_SUPPORTED_VERSIONS); | |
| if (svs != null) { | |
| serverVersion = // could be null | |
| ProtocolVersion.valueOf(svs.selectedVersion); | |
| } else { | |
| serverVersion = serverHello.serverVersion; | |
| } | |
| if (!chc.activeProtocols.contains(serverVersion)) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "The server selected protocol version " + serverVersion + | |
| " is not accepted by client preferences " + | |
| chc.activeProtocols); | |
| } | |
| chc.negotiatedProtocol = serverVersion; | |
| if (!chc.conContext.isNegotiated) { | |
| chc.conContext.protocolVersion = chc.negotiatedProtocol; | |
| chc.conContext.outputRecord.setVersion(chc.negotiatedProtocol); | |
| } | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine( | |
| "Negotiated protocol version: " + serverVersion.name); | |
| } | |
| // Protocol version is negotiated, update locally supported | |
| // signature schemes according to the protocol being used. | |
| SignatureScheme.updateHandshakeLocalSupportedAlgs(chc); | |
| if (serverHello.serverRandom.isVersionDowngrade(chc)) { | |
| throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "A potential protocol version downgrade attack"); | |
| } | |
| // Consume the handshake message for the specific protocol version. | |
| if (serverVersion.isDTLS) { | |
| if (serverVersion.useTLS13PlusSpec()) { | |
| d13HandshakeConsumer.consume(chc, serverHello); | |
| } else { | |
| // TLS 1.3 key share extension may have produced client | |
| // possessions for TLS 1.3 key exchanges. | |
| chc.handshakePossessions.clear(); | |
| d12HandshakeConsumer.consume(chc, serverHello); | |
| } | |
| } else { | |
| if (serverVersion.useTLS13PlusSpec()) { | |
| t13HandshakeConsumer.consume(chc, serverHello); | |
| } else { | |
| // TLS 1.3 key share extension may have produced client | |
| // possessions for TLS 1.3 key exchanges. | |
| chc.handshakePossessions.clear(); | |
| t12HandshakeConsumer.consume(chc, serverHello); | |
| } | |
| } | |
| } | |
| } | |
| private static final | |
| class T12ServerHelloConsumer implements HandshakeConsumer { | |
| // Prevent instantiation of this class. | |
| private T12ServerHelloConsumer() { | |
| // blank | |
| } | |
| @Override | |
| public void consume(ConnectionContext context, | |
| HandshakeMessage message) throws IOException { | |
| // The consuming happens in client side only. | |
| ClientHandshakeContext chc = (ClientHandshakeContext)context; | |
| ServerHelloMessage serverHello = (ServerHelloMessage)message; | |
| if (!chc.isNegotiable(serverHello.serverVersion)) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "Server chose " + serverHello.serverVersion + | |
| ", but that protocol version is not enabled or " + | |
| "not supported by the client."); | |
| } | |
| // chc.negotiatedProtocol = serverHello.serverVersion; | |
| chc.negotiatedCipherSuite = serverHello.cipherSuite; | |
| chc.handshakeHash.determine( | |
| chc.negotiatedProtocol, chc.negotiatedCipherSuite); | |
| chc.serverHelloRandom = serverHello.serverRandom; | |
| if (chc.negotiatedCipherSuite.keyExchange == null) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "TLS 1.2 or prior version does not support the " + | |
| "server cipher suite: " + chc.negotiatedCipherSuite.name); | |
| } | |
| // | |
| // validate | |
| // | |
| // Check and launch the "renegotiation_info" extension. | |
| SSLExtension[] extTypes = new SSLExtension[] { | |
| SSLExtension.SH_RENEGOTIATION_INFO | |
| }; | |
| serverHello.extensions.consumeOnLoad(chc, extTypes); | |
| // Is it session resuming? | |
| if (chc.resumingSession != null) { | |
| // we tried to resume, let's see what the server decided | |
| if (serverHello.sessionId.equals( | |
| chc.resumingSession.getSessionId())) { | |
| // server resumed the session, let's make sure everything | |
| // checks out | |
| // Verify that the session ciphers are unchanged. | |
| CipherSuite sessionSuite = chc.resumingSession.getSuite(); | |
| if (chc.negotiatedCipherSuite != sessionSuite) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "Server returned wrong cipher suite for session"); | |
| } | |
| // verify protocol version match | |
| ProtocolVersion sessionVersion = | |
| chc.resumingSession.getProtocolVersion(); | |
| if (chc.negotiatedProtocol != sessionVersion) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "Server resumed with wrong protocol version"); | |
| } | |
| // looks fine; resume it. | |
| chc.isResumption = true; | |
| chc.resumingSession.setAsSessionResumption(true); | |
| chc.handshakeSession = chc.resumingSession; | |
| } else { | |
| // we wanted to resume, but the server refused | |
| // | |
| // Invalidate the session for initial handshake in case | |
| // of reusing next time. | |
| chc.resumingSession.invalidate(); | |
| chc.resumingSession = null; | |
| chc.isResumption = false; | |
| if (!chc.sslConfig.enableSessionCreation) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "New session creation is disabled"); | |
| } | |
| } | |
| } | |
| // Check and launch ClientHello extensions. | |
| extTypes = chc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.SERVER_HELLO); | |
| serverHello.extensions.consumeOnLoad(chc, extTypes); | |
| if (!chc.isResumption) { | |
| if (chc.resumingSession != null) { | |
| // in case the resumption happens next time. | |
| chc.resumingSession.invalidate(); | |
| chc.resumingSession = null; | |
| } | |
| if (!chc.sslConfig.enableSessionCreation) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "New session creation is disabled"); | |
| } | |
| if (serverHello.sessionId.length() == 0 && | |
| chc.statelessResumption) { | |
| SessionId newId = new SessionId(true, | |
| chc.sslContext.getSecureRandom()); | |
| chc.handshakeSession = new SSLSessionImpl(chc, | |
| chc.negotiatedCipherSuite, newId); | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine("Locally assigned Session Id: " + | |
| newId.toString()); | |
| } | |
| } else { | |
| chc.handshakeSession = new SSLSessionImpl(chc, | |
| chc.negotiatedCipherSuite, | |
| serverHello.sessionId); | |
| } | |
| chc.handshakeSession.setMaximumPacketSize( | |
| chc.sslConfig.maximumPacketSize); | |
| } | |
| chc.handshakeSession.setRandoms(chc.clientHelloRandom, | |
| chc.serverHelloRandom); | |
| // | |
| // update | |
| // | |
| serverHello.extensions.consumeOnTrade(chc, extTypes); | |
| // update the consumers and producers | |
| if (chc.isResumption) { | |
| SSLTrafficKeyDerivation kdg = | |
| SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); | |
| if (kdg == null) { | |
| // unlikely | |
| throw chc.conContext.fatal(Alert.INTERNAL_ERROR, | |
| "Not supported key derivation: " + | |
| chc.negotiatedProtocol); | |
| } else { | |
| chc.handshakeKeyDerivation = kdg.createKeyDerivation( | |
| chc, chc.resumingSession.getMasterSecret()); | |
| } | |
| if (chc.statelessResumption) { | |
| chc.handshakeConsumers.putIfAbsent( | |
| SSLHandshake.NEW_SESSION_TICKET.id, | |
| SSLHandshake.NEW_SESSION_TICKET); | |
| } | |
| chc.conContext.consumers.putIfAbsent( | |
| ContentType.CHANGE_CIPHER_SPEC.id, | |
| ChangeCipherSpec.t10Consumer); | |
| chc.handshakeConsumers.put( | |
| SSLHandshake.FINISHED.id, | |
| SSLHandshake.FINISHED); | |
| } else { | |
| SSLKeyExchange ke = SSLKeyExchange.valueOf( | |
| chc.negotiatedCipherSuite.keyExchange, | |
| chc.negotiatedProtocol); | |
| chc.handshakeKeyExchange = ke; | |
| if (ke != null) { | |
| for (SSLHandshake handshake : | |
| ke.getRelatedHandshakers(chc)) { | |
| chc.handshakeConsumers.put(handshake.id, handshake); | |
| } | |
| } | |
| chc.handshakeConsumers.put(SSLHandshake.SERVER_HELLO_DONE.id, | |
| SSLHandshake.SERVER_HELLO_DONE); | |
| } | |
| // | |
| // produce | |
| // | |
| // Need no new handshake message producers here. | |
| } | |
| } | |
| private static void setUpPskKD(HandshakeContext hc, | |
| SecretKey psk) throws SSLHandshakeException { | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.fine("Using PSK to derive early secret"); | |
| } | |
| try { | |
| CipherSuite.HashAlg hashAlg = hc.negotiatedCipherSuite.hashAlg; | |
| KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm); | |
| SecretKey earlySecret = hkdf.deriveKey("TlsEarlySecret", | |
| HKDFParameterSpec.ofExtract().addIKM(psk) | |
| .addSalt(new byte[hashAlg.hashLength]).extractOnly()); | |
| hc.handshakeKeyDerivation = | |
| new SSLSecretDerivation(hc, earlySecret); | |
| } catch (GeneralSecurityException gse) { | |
| throw new SSLHandshakeException("Could not generate secret", gse); | |
| } | |
| } | |
| private static final | |
| class T13ServerHelloConsumer implements HandshakeConsumer { | |
| // Prevent instantiation of this class. | |
| private T13ServerHelloConsumer() { | |
| // blank | |
| } | |
| @Override | |
| public void consume(ConnectionContext context, | |
| HandshakeMessage message) throws IOException { | |
| // The consuming happens in client side only. | |
| ClientHandshakeContext chc = (ClientHandshakeContext)context; | |
| ServerHelloMessage serverHello = (ServerHelloMessage)message; | |
| if (serverHello.serverVersion != ProtocolVersion.TLS12) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "The ServerHello.legacy_version field is not TLS 1.2"); | |
| } | |
| chc.negotiatedCipherSuite = serverHello.cipherSuite; | |
| chc.handshakeHash.determine( | |
| chc.negotiatedProtocol, chc.negotiatedCipherSuite); | |
| chc.serverHelloRandom = serverHello.serverRandom; | |
| // | |
| // validate | |
| // | |
| // Check and launch ServerHello extensions. | |
| SSLExtension[] extTypes = chc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.SERVER_HELLO); | |
| serverHello.extensions.consumeOnLoad(chc, extTypes); | |
| if (!chc.isResumption) { | |
| if (chc.resumingSession != null) { | |
| // in case the resumption happens next time. | |
| chc.resumingSession.invalidate(); | |
| chc.resumingSession = null; | |
| } | |
| if (!chc.sslConfig.enableSessionCreation) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "New session creation is disabled"); | |
| } | |
| chc.handshakeSession = new SSLSessionImpl(chc, | |
| chc.negotiatedCipherSuite, | |
| serverHello.sessionId); | |
| chc.handshakeSession.setMaximumPacketSize( | |
| chc.sslConfig.maximumPacketSize); | |
| } else { | |
| // The PSK is consumed to allow it to be deleted | |
| SecretKey psk = | |
| chc.resumingSession.consumePreSharedKey(); | |
| if(psk == null) { | |
| throw chc.conContext.fatal(Alert.INTERNAL_ERROR, | |
| "No PSK available. Unable to resume."); | |
| } | |
| chc.handshakeSession = chc.resumingSession; | |
| setUpPskKD(chc, psk); | |
| } | |
| // | |
| // update | |
| // | |
| serverHello.extensions.consumeOnTrade(chc, extTypes); | |
| // Change client/server handshake traffic secrets. | |
| // Refresh handshake hash | |
| chc.handshakeHash.update(); | |
| SSLKeyExchange ke = chc.handshakeKeyExchange; | |
| if (ke == null) { | |
| // unlikely | |
| throw chc.conContext.fatal(Alert.INTERNAL_ERROR, | |
| "Not negotiated key shares"); | |
| } | |
| SSLKeyDerivation handshakeKD = ke.createKeyDerivation(chc); | |
| SecretKey handshakeSecret = handshakeKD.deriveKey( | |
| "TlsHandshakeSecret"); | |
| SSLTrafficKeyDerivation kdg = | |
| SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); | |
| if (kdg == null) { | |
| // unlikely | |
| throw chc.conContext.fatal(Alert.INTERNAL_ERROR, | |
| "Not supported key derivation: " + | |
| chc.negotiatedProtocol); | |
| } | |
| SSLKeyDerivation secretKD = | |
| new SSLSecretDerivation(chc, handshakeSecret); | |
| // update the handshake traffic read keys. | |
| SecretKey readSecret = secretKD.deriveKey( | |
| "TlsServerHandshakeTrafficSecret"); | |
| SSLKeyDerivation readKD = | |
| kdg.createKeyDerivation(chc, readSecret); | |
| SecretKey readKey = readKD.deriveKey("TlsKey"); | |
| IvParameterSpec readIv = | |
| new IvParameterSpec(readKD.deriveData("TlsIv")); | |
| SSLReadCipher readCipher; | |
| try { | |
| readCipher = | |
| chc.negotiatedCipherSuite.bulkCipher.createReadCipher( | |
| Authenticator.valueOf(chc.negotiatedProtocol), | |
| chc.negotiatedProtocol, readKey, readIv, | |
| chc.sslContext.getSecureRandom()); | |
| } catch (GeneralSecurityException gse) { | |
| // unlikely | |
| throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "Missing cipher algorithm", gse); | |
| } | |
| if (readCipher == null) { | |
| throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "Illegal cipher suite (" + chc.negotiatedCipherSuite + | |
| ") and protocol version (" + chc.negotiatedProtocol + | |
| ")"); | |
| } | |
| chc.baseReadSecret = readSecret; | |
| chc.conContext.inputRecord.changeReadCiphers(readCipher); | |
| // update the handshake traffic write keys. | |
| SecretKey writeSecret = secretKD.deriveKey( | |
| "TlsClientHandshakeTrafficSecret"); | |
| SSLKeyDerivation writeKD = | |
| kdg.createKeyDerivation(chc, writeSecret); | |
| SecretKey writeKey = writeKD.deriveKey("TlsKey"); | |
| IvParameterSpec writeIv = | |
| new IvParameterSpec(writeKD.deriveData("TlsIv")); | |
| SSLWriteCipher writeCipher; | |
| try { | |
| writeCipher = | |
| chc.negotiatedCipherSuite.bulkCipher.createWriteCipher( | |
| Authenticator.valueOf(chc.negotiatedProtocol), | |
| chc.negotiatedProtocol, writeKey, writeIv, | |
| chc.sslContext.getSecureRandom()); | |
| } catch (GeneralSecurityException gse) { | |
| // unlikely | |
| throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "Missing cipher algorithm", gse); | |
| } | |
| if (writeCipher == null) { | |
| throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, | |
| "Illegal cipher suite (" + chc.negotiatedCipherSuite + | |
| ") and protocol version (" + chc.negotiatedProtocol + | |
| ")"); | |
| } | |
| chc.baseWriteSecret = writeSecret; | |
| chc.conContext.outputRecord.changeWriteCiphers( | |
| writeCipher, (serverHello.sessionId.length() != 0)); | |
| // Should use resumption_master_secret for TLS 1.3. | |
| // chc.handshakeSession.setMasterSecret(masterSecret); | |
| // Update the context for master key derivation. | |
| chc.handshakeKeyDerivation = secretKD; | |
| // update the consumers and producers | |
| // | |
| // The server sends a dummy change_cipher_spec record immediately | |
| // after its first handshake message. This may either be after a | |
| // ServerHello or a HelloRetryRequest. | |
| chc.conContext.consumers.putIfAbsent( | |
| ContentType.CHANGE_CIPHER_SPEC.id, | |
| ChangeCipherSpec.t13Consumer); | |
| chc.handshakeConsumers.put( | |
| SSLHandshake.ENCRYPTED_EXTENSIONS.id, | |
| SSLHandshake.ENCRYPTED_EXTENSIONS); | |
| // Support cert authentication only, when not PSK. | |
| chc.handshakeConsumers.put( | |
| SSLHandshake.CERTIFICATE_REQUEST.id, | |
| SSLHandshake.CERTIFICATE_REQUEST); | |
| chc.handshakeConsumers.put( | |
| SSLHandshake.CERTIFICATE.id, | |
| SSLHandshake.CERTIFICATE); | |
| chc.handshakeConsumers.put( | |
| SSLHandshake.CERTIFICATE_VERIFY.id, | |
| SSLHandshake.CERTIFICATE_VERIFY); | |
| chc.handshakeConsumers.put( | |
| SSLHandshake.FINISHED.id, | |
| SSLHandshake.FINISHED); | |
| // | |
| // produce | |
| // | |
| // Need no new handshake message producers here. | |
| } | |
| } | |
| private static final | |
| class T13HelloRetryRequestConsumer implements HandshakeConsumer { | |
| // Prevent instantiation of this class. | |
| private T13HelloRetryRequestConsumer() { | |
| // blank | |
| } | |
| @Override | |
| public void consume(ConnectionContext context, | |
| HandshakeMessage message) throws IOException { | |
| // The consuming happens in client side only. | |
| ClientHandshakeContext chc = (ClientHandshakeContext)context; | |
| ServerHelloMessage helloRetryRequest = (ServerHelloMessage)message; | |
| if (helloRetryRequest.serverVersion != ProtocolVersion.TLS12) { | |
| throw chc.conContext.fatal(Alert.PROTOCOL_VERSION, | |
| "The HelloRetryRequest.legacy_version is not TLS 1.2"); | |
| } | |
| chc.negotiatedCipherSuite = helloRetryRequest.cipherSuite; | |
| // | |
| // validate | |
| // | |
| // Check and launch ClientHello extensions. | |
| SSLExtension[] extTypes = chc.sslConfig.getEnabledExtensions( | |
| SSLHandshake.HELLO_RETRY_REQUEST); | |
| helloRetryRequest.extensions.consumeOnLoad(chc, extTypes); | |
| // | |
| // update | |
| // | |
| helloRetryRequest.extensions.consumeOnTrade(chc, extTypes); | |
| // Change client/server handshake traffic secrets. | |
| // Refresh handshake hash | |
| chc.handshakeHash.finish(); // reset the handshake hash | |
| // calculate the transcript hash of the 1st ClientHello message | |
| HandshakeOutStream hos = new HandshakeOutStream(null); | |
| try { | |
| chc.initialClientHelloMsg.write(hos); | |
| } catch (IOException ioe) { | |
| // unlikely | |
| throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "Failed to construct message hash", ioe); | |
| } | |
| chc.handshakeHash.deliver(hos.toByteArray()); | |
| chc.handshakeHash.determine( | |
| chc.negotiatedProtocol, chc.negotiatedCipherSuite); | |
| byte[] clientHelloHash = chc.handshakeHash.digest(); | |
| // calculate the message_hash | |
| // | |
| // Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) = | |
| // Hash(message_hash || /* Handshake type */ | |
| // 00 00 Hash.length || /* Handshake message length (bytes) */ | |
| // Hash(ClientHello1) || /* Hash of ClientHello1 */ | |
| // HelloRetryRequest || ... || Mn) | |
| int hashLen = chc.negotiatedCipherSuite.hashAlg.hashLength; | |
| byte[] hashedClientHello = new byte[4 + hashLen]; | |
| hashedClientHello[0] = SSLHandshake.MESSAGE_HASH.id; | |
| hashedClientHello[1] = (byte)0x00; | |
| hashedClientHello[2] = (byte)0x00; | |
| hashedClientHello[3] = (byte)(hashLen & 0xFF); | |
| System.arraycopy(clientHelloHash, 0, | |
| hashedClientHello, 4, hashLen); | |
| chc.handshakeHash.finish(); // reset the handshake hash | |
| chc.handshakeHash.deliver(hashedClientHello); | |
| int hrrBodyLen = helloRetryRequest.handshakeRecord.remaining(); | |
| byte[] hrrMessage = new byte[4 + hrrBodyLen]; | |
| hrrMessage[0] = SSLHandshake.HELLO_RETRY_REQUEST.id; | |
| hrrMessage[1] = (byte)((hrrBodyLen >> 16) & 0xFF); | |
| hrrMessage[2] = (byte)((hrrBodyLen >> 8) & 0xFF); | |
| hrrMessage[3] = (byte)(hrrBodyLen & 0xFF); | |
| ByteBuffer hrrBody = helloRetryRequest.handshakeRecord.duplicate(); | |
| hrrBody.get(hrrMessage, 4, hrrBodyLen); | |
| chc.handshakeHash.receive(hrrMessage); | |
| // Update the initial ClientHello handshake message. | |
| chc.initialClientHelloMsg.extensions.reproduce(chc, | |
| new SSLExtension[] { | |
| SSLExtension.CH_COOKIE, | |
| SSLExtension.CH_KEY_SHARE, | |
| SSLExtension.CH_PRE_SHARED_KEY | |
| }); | |
| // | |
| // produce response handshake message | |
| // | |
| SSLHandshake.CLIENT_HELLO.produce(context, helloRetryRequest); | |
| } | |
| } | |
| } |