blob: 626fbd5c13b192214f403b977205ceb169d7328a [file] [log] [blame]
/*
* 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 javax.net.ssl.SSLPeerUnverifiedException;
import jdk.internal.event.EventHelper;
import jdk.internal.event.TLSHandshakeEvent;
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) {
throw 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) {
throw context.conContext.fatal(Alert.DECODE_ERROR,
"Inappropriate finished message: need " + verifyDataLen +
" but remaining " + 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) {
throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
"Failed to generate verify_data", ioe);
}
if (!MessageDigest.isEqual(myVerifyData, verifyData)) {
throw context.conContext.fatal(Alert.DECRYPT_ERROR,
"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:
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.finish();
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.finish();
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 consumer 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)) {
throw 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.finish();
chc.conContext.protocolVersion = chc.negotiatedProtocol;
// handshake context cleanup.
chc.handshakeFinished = true;
recordEvent(chc.conContext.conSession);
// 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 {
// Make sure that any expected CertificateVerify message
// has been received and processed.
if (!shc.isResumption) {
if (shc.handshakeConsumers.containsKey(
SSLHandshake.CERTIFICATE_VERIFY.id)) {
throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
"Unexpected Finished handshake message");
}
}
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.finish();
shc.conContext.protocolVersion = shc.negotiatedProtocol;
// handshake context cleanup.
shc.handshakeFinished = true;
recordEvent(shc.conContext.conSession);
// 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
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
"no key derivation");
}
SSLTrafficKeyDerivation kdg =
SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
if (kdg == null) {
// unlikely
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
"Not supported key derivation: " +
chc.negotiatedProtocol);
}
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());
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, false);
} catch (GeneralSecurityException gse) {
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
"Failure to derive application secrets", gse);
}
// 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.finish();
chc.conContext.protocolVersion = chc.negotiatedProtocol;
// handshake context cleanup.
chc.handshakeFinished = true;
chc.conContext.finishHandshake();
recordEvent(chc.conContext.conSession);
// 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
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
"no key derivation");
}
SSLTrafficKeyDerivation kdg =
SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
if (kdg == null) {
// unlikely
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
"Not supported key derivation: " +
shc.negotiatedProtocol);
}
// 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());
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, false);
// update the context for the following key derivation
shc.handshakeKeyDerivation = secretKD;
} catch (GeneralSecurityException gse) {
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
"Failure to derive application secrets", gse);
}
/*
* 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 {
// Make sure that any expected CertificateVerify message
// has been received and processed.
if (!chc.isResumption) {
if (chc.handshakeConsumers.containsKey(
SSLHandshake.CERTIFICATE.id) ||
chc.handshakeConsumers.containsKey(
SSLHandshake.CERTIFICATE_VERIFY.id)) {
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
"Unexpected Finished handshake message");
}
}
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
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
"no key derivation");
}
SSLTrafficKeyDerivation kdg =
SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
if (kdg == null) {
// unlikely
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
"Not supported key derivation: " +
chc.negotiatedProtocol);
}
// save the session
if (!chc.isResumption && chc.handshakeSession.isRejoinable()) {
SSLSessionContextImpl sessionContext = (SSLSessionContextImpl)
chc.sslContext.engineGetClientSessionContext();
sessionContext.put(chc.handshakeSession);
}
// 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());
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 context for the following key derivation
chc.handshakeKeyDerivation = secretKD;
} catch (GeneralSecurityException gse) {
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
"Failure to derive application secrets", gse);
}
//
// 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 {
// Make sure that any expected CertificateVerify message
// has been received and processed.
if (!shc.isResumption) {
if (shc.handshakeConsumers.containsKey(
SSLHandshake.CERTIFICATE.id) ||
shc.handshakeConsumers.containsKey(
SSLHandshake.CERTIFICATE_VERIFY.id)) {
throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
"Unexpected Finished handshake message");
}
}
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
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
"no key derivation");
}
SSLTrafficKeyDerivation kdg =
SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
if (kdg == null) {
// unlikely
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
"Not supported key derivation: " +
shc.negotiatedProtocol);
}
// save the session
if (!shc.isResumption && shc.handshakeSession.isRejoinable()) {
SSLSessionContextImpl sessionContext = (SSLSessionContextImpl)
shc.sslContext.engineGetServerSessionContext();
sessionContext.put(shc.handshakeSession);
}
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());
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);
// 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) {
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
"Failure to derive application secrets", gse);
}
// update connection context
shc.conContext.conSession = shc.handshakeSession.finish();
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();
}
recordEvent(shc.conContext.conSession);
//
// produce
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Sending new session ticket");
}
NewSessionTicket.kickstartProducer.produce(shc);
}
}
private static void recordEvent(SSLSessionImpl session) {
TLSHandshakeEvent event = new TLSHandshakeEvent();
if (event.shouldCommit() || EventHelper.isLoggingSecurity()) {
int peerCertificateId = 0;
try {
// use hash code for Id
peerCertificateId = session
.getCertificateChain()[0]
.hashCode();
} catch (SSLPeerUnverifiedException e) {
// not verified msg
}
if (event.shouldCommit()) {
event.peerHost = session.getPeerHost();
event.peerPort = session.getPeerPort();
event.cipherSuite = session.getCipherSuite();
event.protocolVersion = session.getProtocol();
event.certificateId = peerCertificateId;
event.commit();
}
if (EventHelper.isLoggingSecurity()) {
EventHelper.logTLSHandshakeEvent(null,
session.getPeerHost(),
session.getPeerPort(),
session.getCipherSuite(),
session.getProtocol(),
peerCertificateId);
}
}
}
}