package sun.security.ssl;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.Locale;
import javax.crypto.SecretKey;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.StandardConstants;
import sun.misc.HexDumpEncoder;
import sun.security.ssl.KrbKeyExchange.KrbPremasterSecret;
import sun.security.ssl.SSLHandshake.HandshakeMessage;
final class KrbClientKeyExchange {
static final SSLConsumer krbHandshakeConsumer =
new KrbClientKeyExchangeConsumer();
static final HandshakeProducer krbHandshakeProducer =
new KrbClientKeyExchangeProducer();
private static final
class KrbClientKeyExchangeMessage extends HandshakeMessage {
private static final String KRB5_CLASS_NAME =
"sun.security.ssl.krb5.KrbClientKeyExchangeHelperImpl";
private static final Class<?> krb5Class = AccessController.doPrivileged(
new PrivilegedAction<Class<?>>() {
@Override
public Class<?> run() {
try {
return Class.forName(KRB5_CLASS_NAME, true, null);
} catch (ClassNotFoundException cnf) {
return null;
}
}
});
private static KrbClientKeyExchangeHelper newKrb5Instance() {
if (krb5Class != null) {
try {
return (KrbClientKeyExchangeHelper)krb5Class.
getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException e) {
throw new AssertionError(e);
}
}
return null;
}
private final KrbClientKeyExchangeHelper krb5Helper;
private KrbClientKeyExchangeMessage(HandshakeContext context) {
super(context);
if ((krb5Helper = newKrb5Instance()) == null)
throw new IllegalStateException("Kerberos is unavailable");
}
KrbClientKeyExchangeMessage(HandshakeContext context,
byte[] preMaster, String serverName,
AccessControlContext acc) throws IOException {
this(context);
krb5Helper.init(preMaster, serverName, acc);
}
KrbClientKeyExchangeMessage(HandshakeContext context,
ByteBuffer message, Object serverKeys,
AccessControlContext acc) throws IOException {
this(context);
byte[] encodedTicket = Record.getBytes16(message);
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine("encoded Kerberos service ticket",
encodedTicket);
}
Record.getBytes16(message);
byte[] encryptedPreMasterSecret = Record.getBytes16(message);
if (encryptedPreMasterSecret != null && SSLLogger.isOn &&
SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine("encrypted Kerberos pre-master secret",
encryptedPreMasterSecret);
}
krb5Helper.init(encodedTicket, encryptedPreMasterSecret,
serverKeys, acc);
}
@Override
SSLHandshake handshakeType() {
return SSLHandshake.CLIENT_KEY_EXCHANGE;
}
@Override
int messageLength() {
return (6 + krb5Helper.getEncodedTicket().length +
krb5Helper.getEncryptedPreMasterSecret().length);
}
@Override
void send(HandshakeOutStream hos) throws IOException {
hos.putBytes16(krb5Helper.getEncodedTicket());
hos.putBytes16(null);
hos.putBytes16(krb5Helper.getEncryptedPreMasterSecret());
}
byte[] getPlainPreMasterSecret() {
return krb5Helper.getPlainPreMasterSecret();
}
Principal getPeerPrincipal() {
return krb5Helper.getPeerPrincipal();
}
Principal getLocalPrincipal() {
return krb5Helper.getLocalPrincipal();
}
@Override
public String toString() {
MessageFormat messageFormat = new MessageFormat(
"\"KRB5 ClientKeyExchange\": '{'\n" +
" \"ticket\": '{'\n" +
"{0}\n" +
" '}'\n" +
" \"pre-master\": '{'\n" +
" \"plain\": '{'\n" +
"{1}\n" +
" '}'\n" +
" \"encrypted\": '{'\n" +
"{2}\n" +
" '}'\n" +
" '}'\n" +
"'}'",
Locale.ENGLISH);
HexDumpEncoder hexEncoder = new HexDumpEncoder();
Object[] messageFields = {
Utilities.indent(
hexEncoder.encodeBuffer(
krb5Helper.getEncodedTicket()), " "),
Utilities.indent(
hexEncoder.encodeBuffer(
krb5Helper.getPlainPreMasterSecret()), " "),
Utilities.indent(
hexEncoder.encodeBuffer(
krb5Helper.getEncryptedPreMasterSecret()), " "),
};
return messageFormat.format(messageFields);
}
}
private static final
class KrbClientKeyExchangeProducer implements HandshakeProducer {
private KrbClientKeyExchangeProducer() {
}
@Override
public byte[] produce(ConnectionContext context,
HandshakeMessage message) throws IOException {
ClientHandshakeContext chc = (ClientHandshakeContext)context;
KrbClientKeyExchangeMessage kerberosMsg = null;
String hostName = null;
if (chc.negotiatedServerName != null) {
if (chc.negotiatedServerName.getType() ==
StandardConstants.SNI_HOST_NAME) {
SNIHostName sniHostName = null;
if (chc.negotiatedServerName instanceof SNIHostName) {
sniHostName = (SNIHostName) chc.negotiatedServerName;
} else {
try {
sniHostName = new SNIHostName(
chc.negotiatedServerName.getEncoded());
} catch (IllegalArgumentException iae) {
}
}
if (sniHostName != null)
hostName = sniHostName.getAsciiName();
}
} else {
hostName = chc.handshakeSession.getPeerHost();
}
try {
KrbPremasterSecret premasterSecret =
KrbPremasterSecret.createPremasterSecret(
chc.negotiatedProtocol,
chc.sslContext.getSecureRandom());
kerberosMsg = new KrbClientKeyExchangeMessage(chc,
premasterSecret.preMaster, hostName,
chc.conContext.acc);
chc.handshakePossessions.add(premasterSecret);
} catch (IOException e) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Error generating KRB premaster secret." +
" Hostname: " + hostName + " - Negotiated" +
" server name: " + chc.negotiatedServerName);
}
throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
"Cannot generate KRB premaster secret", e);
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Produced KRB5 ClientKeyExchange handshake message",
kerberosMsg);
}
chc.handshakeSession.setPeerPrincipal(kerberosMsg.getPeerPrincipal());
chc.handshakeSession.setLocalPrincipal(kerberosMsg.getLocalPrincipal());
kerberosMsg.write(chc.handshakeOutput);
chc.handshakeOutput.flush();
SSLKeyExchange ke = SSLKeyExchange.valueOf(
chc.negotiatedCipherSuite.keyExchange,
chc.negotiatedProtocol);
if (ke == null) {
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
"Not supported key exchange type");
} else {
SSLKeyDerivation masterKD = ke.createKeyDerivation(chc);
SecretKey masterSecret =
masterKD.deriveKey("MasterSecret", null);
chc.handshakeSession.setMasterSecret(masterSecret);
SSLTrafficKeyDerivation kd =
SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
if (kd == null) {
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
"Not supported key derivation: " +
chc.negotiatedProtocol);
} else {
chc.handshakeKeyDerivation =
kd.createKeyDerivation(chc, masterSecret);
}
}
return null;
}
}
private static final
class KrbClientKeyExchangeConsumer implements SSLConsumer {
private KrbClientKeyExchangeConsumer() {
}
@Override
public void consume(ConnectionContext context,
ByteBuffer message) throws IOException {
ServerHandshakeContext shc = (ServerHandshakeContext)context;
Object serviceCreds = null;
for (SSLPossession possession : shc.handshakePossessions) {
if (possession instanceof KrbKeyExchange.KrbServiceCreds) {
serviceCreds = ((KrbKeyExchange.KrbServiceCreds) possession)
.serviceCreds;
break;
}
}
if (serviceCreds == null) {
throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
"No Kerberos service credentials for KRB Client Key Exchange");
}
KrbClientKeyExchangeMessage kerberosMsg =
new KrbClientKeyExchangeMessage(shc,
message, serviceCreds, shc.conContext.acc);
KrbPremasterSecret premasterSecret = KrbPremasterSecret.decode(
shc.negotiatedProtocol,
ProtocolVersion.valueOf(shc.clientHelloVersion),
kerberosMsg.getPlainPreMasterSecret(),
shc.sslContext.getSecureRandom());
shc.handshakeSession.setPeerPrincipal(kerberosMsg.getPeerPrincipal());
shc.handshakeSession.setLocalPrincipal(kerberosMsg.getLocalPrincipal());
shc.handshakeCredentials.add(premasterSecret);
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Consuming KRB5 ClientKeyExchange handshake message",
kerberosMsg);
}
SSLKeyExchange ke = SSLKeyExchange.valueOf(
shc.negotiatedCipherSuite.keyExchange,
shc.negotiatedProtocol);
if (ke == null) {
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
"Not supported key exchange type");
} else {
SSLKeyDerivation masterKD = ke.createKeyDerivation(shc);
SecretKey masterSecret =
masterKD.deriveKey("MasterSecret", null);
shc.handshakeSession.setMasterSecret(masterSecret);
SSLTrafficKeyDerivation kd =
SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
if (kd == null) {
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
"Not supported key derivation: " +
shc.negotiatedProtocol);
} else {
shc.handshakeKeyDerivation =
kd.createKeyDerivation(shc, masterSecret);
}
}
}
}
}