--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/Finished.java Fri May 11 15:53:12 2018 -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);
+
+ }
+ }
+}