|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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.misc.HexDumpEncoder; |
|
|
|
|
|
|
|
*/ |
|
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(); |
|
|
|
|
|
|
|
*/ |
|
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: |
|
return VerifyDataScheme.TLS10; |
|
case TLS12: |
|
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."); |
|
} |
|
} |
|
|
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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); |
|
} |
|
} |
|
} |
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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); |
|
} |
|
} |
|
} |
|
|
|
|
|
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 { |
|
|
|
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); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class T12FinishedProducer implements HandshakeProducer { |
|
|
|
private T12FinishedProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
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 { |
|
|
|
chc.handshakeHash.update(); |
|
|
|
FinishedMessage fm = new FinishedMessage(chc); |
|
|
|
|
|
ChangeCipherSpec.t10Producer.produce(chc, message); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Produced client Finished handshake message", fm); |
|
} |
|
|
|
|
|
fm.write(chc.handshakeOutput); |
|
chc.handshakeOutput.flush(); |
|
|
|
|
|
|
|
*/ |
|
if (chc.conContext.secureRenegotiation) { |
|
chc.conContext.clientVerifyData = fm.verifyData; |
|
} |
|
|
|
|
|
if (!chc.isResumption) { |
|
chc.conContext.consumers.put(ContentType.CHANGE_CIPHER_SPEC.id, |
|
ChangeCipherSpec.t10Consumer); |
|
chc.handshakeConsumers.put( |
|
SSLHandshake.FINISHED.id, SSLHandshake.FINISHED); |
|
} else { |
|
if (chc.handshakeSession.isRejoinable()) { |
|
((SSLSessionContextImpl)chc.sslContext. |
|
engineGetClientSessionContext()).put( |
|
chc.handshakeSession); |
|
} |
|
chc.conContext.conSession = chc.handshakeSession.finish(); |
|
chc.conContext.protocolVersion = chc.negotiatedProtocol; |
|
|
|
|
|
chc.handshakeFinished = true; |
|
|
|
chc.conContext.finishHandshake(); |
|
} |
|
|
|
|
|
return null; |
|
} |
|
|
|
private byte[] onProduceFinished(ServerHandshakeContext shc, |
|
HandshakeMessage message) throws IOException { |
|
|
|
shc.handshakeHash.update(); |
|
|
|
FinishedMessage fm = new FinishedMessage(shc); |
|
|
|
|
|
ChangeCipherSpec.t10Producer.produce(shc, message); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Produced server Finished handshake message", fm); |
|
} |
|
|
|
|
|
fm.write(shc.handshakeOutput); |
|
shc.handshakeOutput.flush(); |
|
|
|
|
|
|
|
*/ |
|
if (shc.conContext.secureRenegotiation) { |
|
shc.conContext.serverVerifyData = fm.verifyData; |
|
} |
|
|
|
|
|
if (shc.isResumption) { |
|
shc.conContext.consumers.put(ContentType.CHANGE_CIPHER_SPEC.id, |
|
ChangeCipherSpec.t10Consumer); |
|
shc.handshakeConsumers.put( |
|
SSLHandshake.FINISHED.id, SSLHandshake.FINISHED); |
|
} else { |
|
if (shc.handshakeSession.isRejoinable()) { |
|
((SSLSessionContextImpl)shc.sslContext. |
|
engineGetServerSessionContext()).put( |
|
shc.handshakeSession); |
|
} |
|
shc.conContext.conSession = shc.handshakeSession.finish(); |
|
shc.conContext.protocolVersion = shc.negotiatedProtocol; |
|
|
|
|
|
shc.handshakeFinished = true; |
|
|
|
shc.conContext.finishHandshake(); |
|
} |
|
|
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static final class T12FinishedConsumer implements SSLConsumer { |
|
|
|
private T12FinishedConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
ByteBuffer message) throws IOException { |
|
|
|
HandshakeContext hc = (HandshakeContext)context; |
|
|
|
|
|
hc.handshakeConsumers.remove(SSLHandshake.FINISHED.id); |
|
|
|
// We should not be processing finished messages unless |
|
|
|
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; |
|
|
|
|
|
chc.handshakeFinished = true; |
|
|
|
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 |
|
|
|
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; |
|
|
|
|
|
shc.handshakeFinished = true; |
|
|
|
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); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class T13FinishedProducer implements HandshakeProducer { |
|
|
|
private T13FinishedProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
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 { |
|
|
|
chc.handshakeHash.update(); |
|
|
|
FinishedMessage fm = new FinishedMessage(chc); |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Produced client Finished handshake message", fm); |
|
} |
|
|
|
|
|
fm.write(chc.handshakeOutput); |
|
chc.handshakeOutput.flush(); |
|
|
|
|
|
if (chc.conContext.secureRenegotiation) { |
|
chc.conContext.clientVerifyData = fm.verifyData; |
|
} |
|
|
|
// update the context |
|
|
|
SSLKeyDerivation kd = chc.handshakeKeyDerivation; |
|
if (kd == null) { |
|
|
|
throw chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"no key derivation"); |
|
} |
|
|
|
SSLTrafficKeyDerivation kdg = |
|
SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); |
|
if (kdg == null) { |
|
|
|
throw chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"Not supported key derivation: " + |
|
chc.negotiatedProtocol); |
|
} |
|
|
|
try { |
|
|
|
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 |
|
|
|
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; |
|
|
|
|
|
chc.handshakeFinished = true; |
|
chc.conContext.finishHandshake(); |
|
|
|
|
|
|
|
return null; |
|
} |
|
|
|
private byte[] onProduceFinished(ServerHandshakeContext shc, |
|
HandshakeMessage message) throws IOException { |
|
|
|
shc.handshakeHash.update(); |
|
|
|
FinishedMessage fm = new FinishedMessage(shc); |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Produced server Finished handshake message", fm); |
|
} |
|
|
|
|
|
fm.write(shc.handshakeOutput); |
|
shc.handshakeOutput.flush(); |
|
|
|
|
|
SSLKeyDerivation kd = shc.handshakeKeyDerivation; |
|
if (kd == null) { |
|
|
|
throw shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"no key derivation"); |
|
} |
|
|
|
SSLTrafficKeyDerivation kdg = |
|
SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); |
|
if (kdg == null) { |
|
|
|
throw shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"Not supported key derivation: " + |
|
shc.negotiatedProtocol); |
|
} |
|
|
|
|
|
try { |
|
SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null); |
|
|
|
|
|
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); |
|
|
|
|
|
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); |
|
|
|
|
|
shc.handshakeKeyDerivation = secretKD; |
|
} catch (GeneralSecurityException gse) { |
|
throw shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"Failure to derive application secrets", gse); |
|
} |
|
|
|
|
|
|
|
*/ |
|
if (shc.conContext.secureRenegotiation) { |
|
shc.conContext.serverVerifyData = fm.verifyData; |
|
} |
|
|
|
|
|
shc.handshakeConsumers.put( |
|
SSLHandshake.FINISHED.id, SSLHandshake.FINISHED); |
|
|
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static final class T13FinishedConsumer implements SSLConsumer { |
|
|
|
private T13FinishedConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
ByteBuffer message) throws IOException { |
|
|
|
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 |
|
|
|
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); |
|
} |
|
|
|
|
|
if (chc.conContext.secureRenegotiation) { |
|
chc.conContext.serverVerifyData = fm.verifyData; |
|
} |
|
|
|
// |
|
// validate |
|
// |
|
// blank |
|
|
|
// |
|
// update |
|
// |
|
// A change_cipher_spec record received after the peer's Finished |
|
|
|
chc.conContext.consumers.remove(ContentType.CHANGE_CIPHER_SPEC.id); |
|
|
|
// Change client/server application traffic secrets. |
|
|
|
chc.handshakeHash.update(); |
|
SSLKeyDerivation kd = chc.handshakeKeyDerivation; |
|
if (kd == null) { |
|
|
|
throw chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"no key derivation"); |
|
} |
|
|
|
SSLTrafficKeyDerivation kdg = |
|
SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); |
|
if (kdg == null) { |
|
|
|
throw chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"Not supported key derivation: " + |
|
chc.negotiatedProtocol); |
|
} |
|
|
|
|
|
if (!chc.isResumption && chc.handshakeSession.isRejoinable()) { |
|
SSLSessionContextImpl sessionContext = (SSLSessionContextImpl) |
|
chc.sslContext.engineGetClientSessionContext(); |
|
sessionContext.put(chc.handshakeSession); |
|
} |
|
|
|
|
|
try { |
|
SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null); |
|
|
|
|
|
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); |
|
|
|
|
|
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); |
|
|
|
|
|
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[] { |
|
|
|
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 |
|
|
|
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 |
|
// |
|
|
|
SSLKeyDerivation kd = shc.handshakeKeyDerivation; |
|
if (kd == null) { |
|
|
|
throw shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"no key derivation"); |
|
} |
|
|
|
SSLTrafficKeyDerivation kdg = |
|
SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); |
|
if (kdg == null) { |
|
|
|
throw shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"Not supported key derivation: " + |
|
shc.negotiatedProtocol); |
|
} |
|
|
|
|
|
if (!shc.isResumption && shc.handshakeSession.isRejoinable()) { |
|
SSLSessionContextImpl sessionContext = (SSLSessionContextImpl) |
|
shc.sslContext.engineGetServerSessionContext(); |
|
sessionContext.put(shc.handshakeSession); |
|
} |
|
|
|
try { |
|
|
|
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 |
|
|
|
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); |
|
} |
|
|
|
|
|
shc.conContext.conSession = shc.handshakeSession.finish(); |
|
shc.conContext.protocolVersion = shc.negotiatedProtocol; |
|
|
|
|
|
shc.handshakeFinished = true; |
|
|
|
shc.conContext.finishHandshake(); |
|
|
|
// |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Sending new session ticket"); |
|
} |
|
NewSessionTicket.kickstartProducer.produce(shc); |
|
|
|
} |
|
} |
|
|
|
} |