--- /dev/null 2018-05-11 10:42:23.849000000 -0700 +++ new/src/java.base/share/classes/sun/security/ssl/Finished.java 2018-05-11 15:09:14.121468400 -0700 @@ -0,0 +1,1068 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.text.MessageFormat; +import java.util.Locale; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import sun.security.internal.spec.TlsPrfParameterSpec; +import sun.security.ssl.CipherSuite.HashAlg; +import static sun.security.ssl.CipherSuite.HashAlg.H_NONE; +import sun.security.ssl.SSLBasicKeyDerivation.SecretSizeSpec; +import sun.security.ssl.SSLCipher.SSLReadCipher; +import sun.security.ssl.SSLCipher.SSLWriteCipher; +import sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.util.HexDumpEncoder; + +/** + * Pack of the Finished handshake message. + */ +final class Finished { + static final SSLConsumer t12HandshakeConsumer = + new T12FinishedConsumer(); + static final HandshakeProducer t12HandshakeProducer = + new T12FinishedProducer(); + + static final SSLConsumer t13HandshakeConsumer = + new T13FinishedConsumer(); + static final HandshakeProducer t13HandshakeProducer = + new T13FinishedProducer(); + + /** + * The Finished handshake message. + */ + private static final class FinishedMessage extends HandshakeMessage { + private final byte[] verifyData; + + FinishedMessage(HandshakeContext context) throws IOException { + super(context); + + VerifyDataScheme vds = + VerifyDataScheme.valueOf(context.negotiatedProtocol); + + byte[] vd = null; + try { + vd = vds.createVerifyData(context, false); + } catch (IOException ioe) { + context.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Failed to generate verify_data", ioe); + } + + this.verifyData = vd; + } + + FinishedMessage(HandshakeContext context, + ByteBuffer m) throws IOException { + super(context); + int verifyDataLen = 12; + if (context.negotiatedProtocol == ProtocolVersion.SSL30) { + verifyDataLen = 36; + } else if (context.negotiatedProtocol.useTLS13PlusSpec()) { + verifyDataLen = + context.negotiatedCipherSuite.hashAlg.hashLength; + } + + if (m.remaining() != verifyDataLen) { + context.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Inappropriate finished message: need " + verifyDataLen + + " but remine " + m.remaining() + " bytes verify_data"); + } + + this.verifyData = new byte[verifyDataLen]; + m.get(verifyData); + + VerifyDataScheme vd = + VerifyDataScheme.valueOf(context.negotiatedProtocol); + byte[] myVerifyData; + try { + myVerifyData = vd.createVerifyData(context, true); + } catch (IOException ioe) { + context.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Failed to generate verify_data", ioe); + return; // make the compiler happy + } + if (!MessageDigest.isEqual(myVerifyData, verifyData)) { + context.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "The Finished message cannot be verified."); + } + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.FINISHED; + } + + @Override + public int messageLength() { + return verifyData.length; + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.write(verifyData); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"Finished\": '{'\n" + + " \"verify data\": '{'\n" + + "{0}\n" + + " '}'" + + "'}'", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + Object[] messageFields = { + Utilities.indent(hexEncoder.encode(verifyData), " "), + }; + return messageFormat.format(messageFields); + } + } + + interface VerifyDataGenerator { + byte[] createVerifyData(HandshakeContext context, + boolean isValidation) throws IOException; + } + + enum VerifyDataScheme { + SSL30 ("kdf_ssl30", new S30VerifyDataGenerator()), + TLS10 ("kdf_tls10", new T10VerifyDataGenerator()), + TLS12 ("kdf_tls12", new T12VerifyDataGenerator()), + TLS13 ("kdf_tls13", new T13VerifyDataGenerator()); + + final String name; + final VerifyDataGenerator generator; + + VerifyDataScheme(String name, VerifyDataGenerator verifyDataGenerator) { + this.name = name; + this.generator = verifyDataGenerator; + } + + static VerifyDataScheme valueOf(ProtocolVersion protocolVersion) { + switch (protocolVersion) { + case SSL30: + return VerifyDataScheme.SSL30; + case TLS10: + case TLS11: + case DTLS10: + return VerifyDataScheme.TLS10; + case TLS12: + case DTLS12: + return VerifyDataScheme.TLS12; + case TLS13: + case DTLS13: + return VerifyDataScheme.TLS13; + default: + return null; + } + } + + public byte[] createVerifyData(HandshakeContext context, + boolean isValidation) throws IOException { + if (generator != null) { + return generator.createVerifyData(context, isValidation); + } + + throw new UnsupportedOperationException("Not supported yet."); + } + } + + // SSL 3.0 + private static final + class S30VerifyDataGenerator implements VerifyDataGenerator { + @Override + public byte[] createVerifyData(HandshakeContext context, + boolean isValidation) throws IOException { + HandshakeHash handshakeHash = context.handshakeHash; + SecretKey masterSecretKey = + context.handshakeSession.getMasterSecret(); + + boolean useClientLabel = + (context.sslConfig.isClientMode && !isValidation) || + (!context.sslConfig.isClientMode && isValidation); + return handshakeHash.digest(useClientLabel, masterSecretKey); + } + } + + // TLS 1.0, TLS 1.1, DTLS 1.0 + private static final + class T10VerifyDataGenerator implements VerifyDataGenerator { + @Override + public byte[] createVerifyData(HandshakeContext context, + boolean isValidation) throws IOException { + HandshakeHash handshakeHash = context.handshakeHash; + SecretKey masterSecretKey = + context.handshakeSession.getMasterSecret(); + + boolean useClientLabel = + (context.sslConfig.isClientMode && !isValidation) || + (!context.sslConfig.isClientMode && isValidation); + String tlsLabel; + if (useClientLabel) { + tlsLabel = "client finished"; + } else { + tlsLabel = "server finished"; + } + + try { + byte[] seed = handshakeHash.digest(); + String prfAlg = "SunTlsPrf"; + HashAlg hashAlg = H_NONE; + + /* + * RFC 5246/7.4.9 says that finished messages can + * be ciphersuite-specific in both length/PRF hash + * algorithm. If we ever run across a different + * length, this call will need to be updated. + */ + @SuppressWarnings("deprecation") + TlsPrfParameterSpec spec = new TlsPrfParameterSpec( + masterSecretKey, tlsLabel, seed, 12, + hashAlg.name, hashAlg.hashLength, hashAlg.blockSize); + KeyGenerator kg = JsseJce.getKeyGenerator(prfAlg); + kg.init(spec); + SecretKey prfKey = kg.generateKey(); + if (!"RAW".equals(prfKey.getFormat())) { + throw new ProviderException( + "Invalid PRF output, format must be RAW. " + + "Format received: " + prfKey.getFormat()); + } + byte[] finished = prfKey.getEncoded(); + return finished; + } catch (GeneralSecurityException e) { + throw new RuntimeException("PRF failed", e); + } + } + } + + // TLS 1.2 + private static final + class T12VerifyDataGenerator implements VerifyDataGenerator { + @Override + public byte[] createVerifyData(HandshakeContext context, + boolean isValidation) throws IOException { + CipherSuite cipherSuite = context.negotiatedCipherSuite; + HandshakeHash handshakeHash = context.handshakeHash; + SecretKey masterSecretKey = + context.handshakeSession.getMasterSecret(); + + boolean useClientLabel = + (context.sslConfig.isClientMode && !isValidation) || + (!context.sslConfig.isClientMode && isValidation); + String tlsLabel; + if (useClientLabel) { + tlsLabel = "client finished"; + } else { + tlsLabel = "server finished"; + } + + try { + byte[] seed = handshakeHash.digest(); + String prfAlg = "SunTls12Prf"; + HashAlg hashAlg = cipherSuite.hashAlg; + + /* + * RFC 5246/7.4.9 says that finished messages can + * be ciphersuite-specific in both length/PRF hash + * algorithm. If we ever run across a different + * length, this call will need to be updated. + */ + @SuppressWarnings("deprecation") + TlsPrfParameterSpec spec = new TlsPrfParameterSpec( + masterSecretKey, tlsLabel, seed, 12, + hashAlg.name, hashAlg.hashLength, hashAlg.blockSize); + KeyGenerator kg = JsseJce.getKeyGenerator(prfAlg); + kg.init(spec); + SecretKey prfKey = kg.generateKey(); + if (!"RAW".equals(prfKey.getFormat())) { + throw new ProviderException( + "Invalid PRF output, format must be RAW. " + + "Format received: " + prfKey.getFormat()); + } + byte[] finished = prfKey.getEncoded(); + return finished; + } catch (GeneralSecurityException e) { + throw new RuntimeException("PRF failed", e); + } + } + } + + // TLS 1.2 + private static final + class T13VerifyDataGenerator implements VerifyDataGenerator { + private static final byte[] hkdfLabel = "tls13 finished".getBytes(); + private static final byte[] hkdfContext = new byte[0]; + + @Override + public byte[] createVerifyData(HandshakeContext context, + boolean isValidation) throws IOException { + // create finished secret key + HashAlg hashAlg = + context.negotiatedCipherSuite.hashAlg; + SecretKey secret = isValidation ? + context.baseReadSecret : context.baseWriteSecret; + SSLBasicKeyDerivation kdf = new SSLBasicKeyDerivation( + secret, hashAlg.name, + hkdfLabel, hkdfContext, hashAlg.hashLength); + AlgorithmParameterSpec keySpec = + new SecretSizeSpec(hashAlg.hashLength); + SecretKey finishedSecret = + kdf.deriveKey("TlsFinishedSecret", keySpec); + + String hmacAlg = + "Hmac" + hashAlg.name.replace("-", ""); + try { + Mac hmac = JsseJce.getMac(hmacAlg); + hmac.init(finishedSecret); + return hmac.doFinal(context.handshakeHash.digest()); + } catch (NoSuchAlgorithmException |InvalidKeyException ex) { + throw new ProviderException( + "Failed to generate verify_data", ex); + } + } + } + + /** + * The "Finished" handshake message producer. + */ + private static final + class T12FinishedProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T12FinishedProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The consuming happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + if (hc.sslConfig.isClientMode) { + return onProduceFinished( + (ClientHandshakeContext)context, message); + } else { + return onProduceFinished( + (ServerHandshakeContext)context, message); + } + } + + private byte[] onProduceFinished(ClientHandshakeContext chc, + HandshakeMessage message) throws IOException { + // Refresh handshake hash + chc.handshakeHash.update(); + + FinishedMessage fm = new FinishedMessage(chc); + + // Change write cipher and delivery ChangeCipherSpec message. + ChangeCipherSpec.t10Producer.produce(chc, message); + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced client Finished handshake message", fm); + } + + // Output the handshake message. + fm.write(chc.handshakeOutput); + chc.handshakeOutput.flush(); + + /* + * save server verify data for secure renegotiation + */ + if (chc.conContext.secureRenegotiation) { + chc.conContext.clientVerifyData = fm.verifyData; + } + + // update the consumers and producers + if (!chc.isResumption) { + chc.conContext.consumers.put(ContentType.CHANGE_CIPHER_SPEC.id, + ChangeCipherSpec.t10Consumer); + chc.handshakeConsumers.put( + SSLHandshake.FINISHED.id, SSLHandshake.FINISHED); + chc.conContext.inputRecord.expectingFinishFlight(); + } else { + if (chc.handshakeSession.isRejoinable()) { + ((SSLSessionContextImpl)chc.sslContext. + engineGetClientSessionContext()).put( + chc.handshakeSession); + } + chc.conContext.conSession = chc.handshakeSession; + chc.conContext.protocolVersion = chc.negotiatedProtocol; + + // handshake context cleanup. + chc.handshakeFinished = true; + + // May need to retransmit the last flight for DTLS. + if (!chc.sslContext.isDTLS()) { + chc.conContext.finishHandshake(); + } + } + + // The handshake message has been delivered. + return null; + } + + private byte[] onProduceFinished(ServerHandshakeContext shc, + HandshakeMessage message) throws IOException { + // Refresh handshake hash + shc.handshakeHash.update(); + + FinishedMessage fm = new FinishedMessage(shc); + + // Change write cipher and delivery ChangeCipherSpec message. + ChangeCipherSpec.t10Producer.produce(shc, message); + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced server Finished handshake message", fm); + } + + // Output the handshake message. + fm.write(shc.handshakeOutput); + shc.handshakeOutput.flush(); + + /* + * save client verify data for secure renegotiation + */ + if (shc.conContext.secureRenegotiation) { + shc.conContext.serverVerifyData = fm.verifyData; + } + + // update the consumers and producers + if (shc.isResumption) { + shc.conContext.consumers.put(ContentType.CHANGE_CIPHER_SPEC.id, + ChangeCipherSpec.t10Consumer); + shc.handshakeConsumers.put( + SSLHandshake.FINISHED.id, SSLHandshake.FINISHED); + shc.conContext.inputRecord.expectingFinishFlight(); + } else { + if (shc.handshakeSession.isRejoinable()) { + ((SSLSessionContextImpl)shc.sslContext. + engineGetServerSessionContext()).put( + shc.handshakeSession); + } + shc.conContext.conSession = shc.handshakeSession; + shc.conContext.protocolVersion = shc.negotiatedProtocol; + + // handshake context cleanup. + shc.handshakeFinished = true; + + // May need to retransmit the last flight for DTLS. + if (!shc.sslContext.isDTLS()) { + shc.conContext.finishHandshake(); + } + } + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "Finished" handshake message consumer. + */ + private static final class T12FinishedConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T12FinishedConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + + // This comsumer can be used only once. + hc.handshakeConsumers.remove(SSLHandshake.FINISHED.id); + + // We should not be processing finished messages unless + // we have received ChangeCipherSpec + if (hc.conContext.consumers.containsKey( + ContentType.CHANGE_CIPHER_SPEC.id)) { + hc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Missing ChangeCipherSpec message"); + } + + if (hc.sslConfig.isClientMode) { + onConsumeFinished((ClientHandshakeContext)context, message); + } else { + onConsumeFinished((ServerHandshakeContext)context, message); + } + } + + private void onConsumeFinished(ClientHandshakeContext chc, + ByteBuffer message) throws IOException { + FinishedMessage fm = new FinishedMessage(chc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming server Finished handshake message", fm); + } + + if (chc.conContext.secureRenegotiation) { + chc.conContext.serverVerifyData = fm.verifyData; + } + + if (!chc.isResumption) { + if (chc.handshakeSession.isRejoinable()) { + ((SSLSessionContextImpl)chc.sslContext. + engineGetClientSessionContext()).put( + chc.handshakeSession); + } + chc.conContext.conSession = chc.handshakeSession; + chc.conContext.protocolVersion = chc.negotiatedProtocol; + + // handshake context cleanup. + chc.handshakeFinished = true; + + // May need to retransmit the last flight for DTLS. + if (!chc.sslContext.isDTLS()) { + chc.conContext.finishHandshake(); + } + } else { + chc.handshakeProducers.put(SSLHandshake.FINISHED.id, + SSLHandshake.FINISHED); + } + + // + // produce + // + SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] { + SSLHandshake.FINISHED + }; + + for (SSLHandshake hs : probableHandshakeMessages) { + HandshakeProducer handshakeProducer = + chc.handshakeProducers.remove(hs.id); + if (handshakeProducer != null) { + handshakeProducer.produce(chc, fm); + } + } + } + + private void onConsumeFinished(ServerHandshakeContext shc, + ByteBuffer message) throws IOException { + FinishedMessage fm = new FinishedMessage(shc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming client Finished handshake message", fm); + } + + if (shc.conContext.secureRenegotiation) { + shc.conContext.clientVerifyData = fm.verifyData; + } + + if (shc.isResumption) { + if (shc.handshakeSession.isRejoinable()) { + ((SSLSessionContextImpl)shc.sslContext. + engineGetServerSessionContext()).put( + shc.handshakeSession); + } + shc.conContext.conSession = shc.handshakeSession; + shc.conContext.protocolVersion = shc.negotiatedProtocol; + + // handshake context cleanup. + shc.handshakeFinished = true; + + // May need to retransmit the last flight for DTLS. + if (!shc.sslContext.isDTLS()) { + shc.conContext.finishHandshake(); + } + } else { + shc.handshakeProducers.put(SSLHandshake.FINISHED.id, + SSLHandshake.FINISHED); + } + + // + // produce + // + SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] { + SSLHandshake.FINISHED + }; + + for (SSLHandshake hs : probableHandshakeMessages) { + HandshakeProducer handshakeProducer = + shc.handshakeProducers.remove(hs.id); + if (handshakeProducer != null) { + handshakeProducer.produce(shc, fm); + } + } + } + } + + /** + * The "Finished" handshake message producer. + */ + private static final + class T13FinishedProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T13FinishedProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The consuming happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + if (hc.sslConfig.isClientMode) { + return onProduceFinished( + (ClientHandshakeContext)context, message); + } else { + return onProduceFinished( + (ServerHandshakeContext)context, message); + } + } + + private byte[] onProduceFinished(ClientHandshakeContext chc, + HandshakeMessage message) throws IOException { + // Refresh handshake hash + chc.handshakeHash.update(); + + FinishedMessage fm = new FinishedMessage(chc); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced client Finished handshake message", fm); + } + + // Output the handshake message. + fm.write(chc.handshakeOutput); + chc.handshakeOutput.flush(); + + // save server verify data for secure renegotiation + if (chc.conContext.secureRenegotiation) { + chc.conContext.clientVerifyData = fm.verifyData; + } + + // update the context + // Change client/server application traffic secrets. + SSLKeyDerivation kd = chc.handshakeKeyDerivation; + if (kd == null) { + // unlikely + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "no key derivation"); + return null; // make the compiler happy + } + + SSLTrafficKeyDerivation kdg = + SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); + if (kdg == null) { + // unlikely + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "Not supported key derivation: " + + chc.negotiatedProtocol); + return null; // make the compiler happy + } + + try { + // update the application traffic read keys. + SecretKey writeSecret = kd.deriveKey( + "TlsClientAppTrafficSecret", null); + + SSLKeyDerivation writeKD = + kdg.createKeyDerivation(chc, writeSecret); + SecretKey writeKey = writeKD.deriveKey( + "TlsKey", null); + SecretKey writeIvSecret = writeKD.deriveKey( + "TlsIv", null); + IvParameterSpec writeIv = + new IvParameterSpec(writeIvSecret.getEncoded()); + SSLWriteCipher writeCipher = + chc.negotiatedCipherSuite.bulkCipher.createWriteCipher( + Authenticator.valueOf(chc.negotiatedProtocol), + chc.negotiatedProtocol, writeKey, writeIv, + chc.sslContext.getSecureRandom()); + + chc.baseWriteSecret = writeSecret; + chc.conContext.outputRecord.changeWriteCiphers( + writeCipher, false); + + } catch (GeneralSecurityException gse) { + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "Failure to derive application secrets", gse); + return null; // make the compiler happy + } + + // The resumption master secret is stored in the session so + // it can be used after the handshake is completed. + SSLSecretDerivation sd = ((SSLSecretDerivation) kd).forContext(chc); + SecretKey resumptionMasterSecret = sd.deriveKey( + "TlsResumptionMasterSecret", null); + chc.handshakeSession.setResumptionMasterSecret(resumptionMasterSecret); + + chc.conContext.conSession = chc.handshakeSession; + chc.conContext.protocolVersion = chc.negotiatedProtocol; + + // handshake context cleanup. + chc.handshakeFinished = true; + chc.conContext.finishHandshake(); + + // The handshake message has been delivered. + return null; + } + + private byte[] onProduceFinished(ServerHandshakeContext shc, + HandshakeMessage message) throws IOException { + // Refresh handshake hash + shc.handshakeHash.update(); + + FinishedMessage fm = new FinishedMessage(shc); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced server Finished handshake message", fm); + } + + // Output the handshake message. + fm.write(shc.handshakeOutput); + shc.handshakeOutput.flush(); + + // Change client/server application traffic secrets. + SSLKeyDerivation kd = shc.handshakeKeyDerivation; + if (kd == null) { + // unlikely + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "no key derivation"); + return null; // make the compiler happy + } + + SSLTrafficKeyDerivation kdg = + SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); + if (kdg == null) { + // unlikely + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "Not supported key derivation: " + + shc.negotiatedProtocol); + return null; // make the compiler happy + } + + // derive salt secret + try { + SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null); + + // derive application secrets + HashAlg hashAlg = shc.negotiatedCipherSuite.hashAlg; + HKDF hkdf = new HKDF(hashAlg.name); + byte[] zeros = new byte[hashAlg.hashLength]; + SecretKeySpec sharedSecret = + new SecretKeySpec(zeros, "TlsZeroSecret"); + SecretKey masterSecret = + hkdf.extract(saltSecret, sharedSecret, "TlsMasterSecret"); + + SSLKeyDerivation secretKD = + new SSLSecretDerivation(shc, masterSecret); + + // update the handshake traffic write keys. + SecretKey writeSecret = secretKD.deriveKey( + "TlsServerAppTrafficSecret", null); + SSLKeyDerivation writeKD = + kdg.createKeyDerivation(shc, writeSecret); + SecretKey writeKey = writeKD.deriveKey( + "TlsKey", null); + SecretKey writeIvSecret = writeKD.deriveKey( + "TlsIv", null); + IvParameterSpec writeIv = + new IvParameterSpec(writeIvSecret.getEncoded()); + SSLWriteCipher writeCipher = + shc.negotiatedCipherSuite.bulkCipher.createWriteCipher( + Authenticator.valueOf(shc.negotiatedProtocol), + shc.negotiatedProtocol, writeKey, writeIv, + shc.sslContext.getSecureRandom()); + + shc.baseWriteSecret = writeSecret; + shc.conContext.outputRecord.changeWriteCiphers( + writeCipher, false); + + // TODO: the exporter_master_secret + + // update the context for the following key derivation + shc.handshakeKeyDerivation = secretKD; + } catch (GeneralSecurityException gse) { + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "Failure to derive application secrets", gse); + return null; // make the compiler happy + } + + /* + * save client verify data for secure renegotiation + */ + if (shc.conContext.secureRenegotiation) { + shc.conContext.serverVerifyData = fm.verifyData; + } + + // update the context + shc.handshakeConsumers.put( + SSLHandshake.FINISHED.id, SSLHandshake.FINISHED); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "Finished" handshake message consumer. + */ + private static final class T13FinishedConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T13FinishedConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + if (hc.sslConfig.isClientMode) { + onConsumeFinished( + (ClientHandshakeContext)context, message); + } else { + onConsumeFinished( + (ServerHandshakeContext)context, message); + } + } + + private void onConsumeFinished(ClientHandshakeContext chc, + ByteBuffer message) throws IOException { + FinishedMessage fm = new FinishedMessage(chc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming server Finished handshake message", fm); + } + + // Save client verify data for secure renegotiation. + if (chc.conContext.secureRenegotiation) { + chc.conContext.serverVerifyData = fm.verifyData; + } + + // + // validate + // + // blank + + // + // update + // + // A change_cipher_spec record received after the peer's Finished + // message MUST be treated as an unexpected record type. + chc.conContext.consumers.remove(ContentType.CHANGE_CIPHER_SPEC.id); + + // Change client/server application traffic secrets. + // Refresh handshake hash + chc.handshakeHash.update(); + SSLKeyDerivation kd = chc.handshakeKeyDerivation; + if (kd == null) { + // unlikely + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "no key derivation"); + return; // make the compiler happy + } + + SSLTrafficKeyDerivation kdg = + SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); + if (kdg == null) { + // unlikely + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "Not supported key derivation: " + + chc.negotiatedProtocol); + return; // make the compiler happy + } + + // derive salt secret + try { + SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null); + + // derive application secrets + HashAlg hashAlg = chc.negotiatedCipherSuite.hashAlg; + HKDF hkdf = new HKDF(hashAlg.name); + byte[] zeros = new byte[hashAlg.hashLength]; + SecretKeySpec sharedSecret = + new SecretKeySpec(zeros, "TlsZeroSecret"); + SecretKey masterSecret = + hkdf.extract(saltSecret, sharedSecret, "TlsMasterSecret"); + + SSLKeyDerivation secretKD = + new SSLSecretDerivation(chc, masterSecret); + + // update the handshake traffic read keys. + SecretKey readSecret = secretKD.deriveKey( + "TlsServerAppTrafficSecret", null); + SSLKeyDerivation writeKD = + kdg.createKeyDerivation(chc, readSecret); + SecretKey readKey = writeKD.deriveKey( + "TlsKey", null); + SecretKey readIvSecret = writeKD.deriveKey( + "TlsIv", null); + IvParameterSpec readIv = + new IvParameterSpec(readIvSecret.getEncoded()); + SSLReadCipher readCipher = + chc.negotiatedCipherSuite.bulkCipher.createReadCipher( + Authenticator.valueOf(chc.negotiatedProtocol), + chc.negotiatedProtocol, readKey, readIv, + chc.sslContext.getSecureRandom()); + + chc.baseReadSecret = readSecret; + chc.conContext.inputRecord.changeReadCiphers(readCipher); + + // TODO: the exporter_master_secret + + // update the context for the following key derivation + chc.handshakeKeyDerivation = secretKD; + } catch (GeneralSecurityException gse) { + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "Failure to derive application secrets", gse); + return; // make the compiler happy + } + + // + // produce + // + chc.handshakeProducers.put(SSLHandshake.FINISHED.id, + SSLHandshake.FINISHED); + SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] { + // full handshake messages + SSLHandshake.CERTIFICATE, + SSLHandshake.CERTIFICATE_VERIFY, + SSLHandshake.FINISHED + }; + + for (SSLHandshake hs : probableHandshakeMessages) { + HandshakeProducer handshakeProducer = + chc.handshakeProducers.remove(hs.id); + if (handshakeProducer != null) { + handshakeProducer.produce(chc, null); + } + } + } + + private void onConsumeFinished(ServerHandshakeContext shc, + ByteBuffer message) throws IOException { + FinishedMessage fm = new FinishedMessage(shc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming client Finished handshake message", fm); + } + + if (shc.conContext.secureRenegotiation) { + shc.conContext.clientVerifyData = fm.verifyData; + } + + // + // validate + // + // blank + + // + // update + // + // Change client/server application traffic secrets. + SSLKeyDerivation kd = shc.handshakeKeyDerivation; + if (kd == null) { + // unlikely + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "no key derivation"); + return; // make the compiler happy + } + + SSLTrafficKeyDerivation kdg = + SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); + if (kdg == null) { + // unlikely + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "Not supported key derivation: " + + shc.negotiatedProtocol); + return; // make the compiler happy + } + + try { + // update the application traffic read keys. + SecretKey readSecret = kd.deriveKey( + "TlsClientAppTrafficSecret", null); + + SSLKeyDerivation readKD = + kdg.createKeyDerivation(shc, readSecret); + SecretKey readKey = readKD.deriveKey( + "TlsKey", null); + SecretKey readIvSecret = readKD.deriveKey( + "TlsIv", null); + IvParameterSpec readIv = + new IvParameterSpec(readIvSecret.getEncoded()); + SSLReadCipher readCipher = + shc.negotiatedCipherSuite.bulkCipher.createReadCipher( + Authenticator.valueOf(shc.negotiatedProtocol), + shc.negotiatedProtocol, readKey, readIv, + shc.sslContext.getSecureRandom()); + + shc.baseReadSecret = readSecret; + shc.conContext.inputRecord.changeReadCiphers(readCipher); + + // The resumption master secret is stored in the session so + // it can be used after the handshake is completed. + shc.handshakeHash.update(); + SSLSecretDerivation sd = ((SSLSecretDerivation)kd).forContext(shc); + SecretKey resumptionMasterSecret = sd.deriveKey( + "TlsResumptionMasterSecret", null); + shc.handshakeSession.setResumptionMasterSecret(resumptionMasterSecret); + } catch (GeneralSecurityException gse) { + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "Failure to derive application secrets", gse); + return; // make the compiler happy + } + + // update connection context + shc.conContext.conSession = shc.handshakeSession; + shc.conContext.protocolVersion = shc.negotiatedProtocol; + + // handshake context cleanup. + shc.handshakeFinished = true; + + // May need to retransmit the last flight for DTLS. + if (!shc.sslContext.isDTLS()) { + shc.conContext.finishHandshake(); + } + + // + // produce + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Sending new session ticket"); + } + NewSessionTicket.kickstartProducer.produce(shc); + + } + } +}