package sun.security.krb5.internal.ssl;
import sun.security.ssl.ClientKeyExchange;
import sun.security.ssl.Debug;
import sun.security.ssl.ClientKeyExchangeService;
import sun.security.ssl.HandshakeOutStream;
import sun.security.jgss.GSSCaller;
import sun.security.jgss.krb5.Krb5Util;
import sun.security.jgss.krb5.ServiceCreds;
import sun.security.krb5.EncryptedData;
import sun.security.krb5.EncryptionKey;
import sun.security.krb5.KrbException;
import sun.security.krb5.PrincipalName;
import sun.security.krb5.internal.EncTicketPart;
import sun.security.krb5.internal.Ticket;
import sun.security.krb5.internal.crypto.KeyUsage;
import sun.security.ssl.ProtocolVersion;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosKey;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.kerberos.KeyTab;
import javax.security.auth.kerberos.ServicePermission;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.util.Set;
public class Krb5KeyExchangeService implements ClientKeyExchangeService {
public static final Debug debug = Debug.getInstance("ssl");
@Override
public String[] supported() {
return new String[] { "KRB5", "KRB5_EXPORT" };
}
@Override
public Object getServiceCreds(AccessControlContext acc) {
try {
ServiceCreds serviceCreds = AccessController.doPrivileged(
(PrivilegedExceptionAction<ServiceCreds>)
() -> Krb5Util.getServiceCreds(
GSSCaller.CALLER_SSL_SERVER, null, acc));
if (serviceCreds == null) {
if (debug != null && Debug.isOn("handshake")) {
System.out.println("Kerberos serviceCreds not available");
}
return null;
}
if (debug != null && Debug.isOn("handshake")) {
System.out.println("Using Kerberos creds");
}
String serverPrincipal = serviceCreds.getName();
if (serverPrincipal != null) {
SecurityManager sm = System.getSecurityManager();
try {
if (sm != null) {
sm.checkPermission(new ServicePermission(
serverPrincipal, "accept"), acc);
}
} catch (SecurityException se) {
if (debug != null && Debug.isOn("handshake")) {
System.out.println("Permission to access Kerberos"
+ " secret key denied");
}
return null;
}
}
return serviceCreds;
} catch (PrivilegedActionException e) {
if (debug != null && Debug.isOn("handshake")) {
System.out.println("Attempt to obtain Kerberos key failed: "
+ e.toString());
}
return null;
}
}
@Override
public String getServiceHostName(Principal principal) {
if (principal == null) {
return null;
}
String hostName = null;
try {
PrincipalName princName =
new PrincipalName(principal.getName(),
PrincipalName.KRB_NT_SRV_HST);
String[] nameParts = princName.getNameStrings();
if (nameParts.length >= 2) {
hostName = nameParts[1];
}
} catch (Exception e) {
}
return hostName;
}
@Override
public boolean isRelated(boolean isClient,
AccessControlContext acc, Principal p) {
if (p == null) return false;
try {
Subject subject = AccessController.doPrivileged(
(PrivilegedExceptionAction<Subject>)
() -> Krb5Util.getSubject(
isClient ? GSSCaller.CALLER_SSL_CLIENT
: GSSCaller.CALLER_SSL_SERVER,
acc));
if (subject == null) {
if (debug != null && Debug.isOn("session")) {
System.out.println("Kerberos credentials are" +
" not present in the current Subject;" +
" check if " +
" javax.security.auth.useSubjectAsCreds" +
" system property has been set to false");
}
return false;
}
Set<Principal> principals =
subject.getPrincipals(Principal.class);
if (principals.contains(p)) {
return true;
} else {
if (isClient) {
return false;
} else {
for (KeyTab pc : subject.getPrivateCredentials(KeyTab.class)) {
if (!pc.isBound()) {
return true;
}
}
return false;
}
}
} catch (PrivilegedActionException pae) {
if (debug != null && Debug.isOn("session")) {
System.out.println("Attempt to obtain" +
" subject failed! " + pae);
}
return false;
}
}
public ClientKeyExchange createClientExchange(
String serverName, AccessControlContext acc,
ProtocolVersion protocolVerson, SecureRandom rand) throws IOException {
return new ExchangerImpl(serverName, acc, protocolVerson, rand);
}
public ClientKeyExchange createServerExchange(
ProtocolVersion protocolVersion, ProtocolVersion clientVersion,
SecureRandom rand, byte[] encodedTicket, byte[] encrypted,
AccessControlContext acc, Object serviceCreds) throws IOException {
return new ExchangerImpl(protocolVersion, clientVersion, rand,
encodedTicket, encrypted, acc, serviceCreds);
}
static class ExchangerImpl extends ClientKeyExchange {
final private KerberosPreMasterSecret preMaster;
final private byte[] encodedTicket;
final private KerberosPrincipal peerPrincipal;
final private KerberosPrincipal localPrincipal;
@Override
public int messageLength() {
return encodedTicket.length + preMaster.getEncrypted().length + 6;
}
@Override
public void send(HandshakeOutStream s) throws IOException {
s.putBytes16(encodedTicket);
s.putBytes16(null);
s.putBytes16(preMaster.getEncrypted());
}
@Override
public void print(PrintStream s) throws IOException {
s.println("*** ClientKeyExchange, Kerberos");
if (debug != null && Debug.isOn("verbose")) {
Debug.println(s, "Kerberos service ticket", encodedTicket);
Debug.println(s, "Random Secret", preMaster.getUnencrypted());
Debug.println(s, "Encrypted random Secret", preMaster.getEncrypted());
}
}
ExchangerImpl(String serverName, AccessControlContext acc,
ProtocolVersion protocolVersion, SecureRandom rand) throws IOException {
KerberosTicket ticket = getServiceTicket(serverName, acc);
encodedTicket = ticket.getEncoded();
peerPrincipal = ticket.getServer();
localPrincipal = ticket.getClient();
EncryptionKey sessionKey = new EncryptionKey(
ticket.getSessionKeyType(),
ticket.getSessionKey().getEncoded());
preMaster = new KerberosPreMasterSecret(protocolVersion,
rand, sessionKey);
}
ExchangerImpl(
ProtocolVersion protocolVersion, ProtocolVersion clientVersion, SecureRandom rand,
byte[] encodedTicket, byte[] encrypted,
AccessControlContext acc, Object serviceCreds) throws IOException {
this.encodedTicket = encodedTicket;
if (debug != null && Debug.isOn("verbose")) {
Debug.println(System.out,
"encoded Kerberos service ticket", encodedTicket);
}
EncryptionKey sessionKey = null;
KerberosPrincipal tmpPeer = null;
KerberosPrincipal tmpLocal = null;
try {
Ticket t = new Ticket(encodedTicket);
EncryptedData encPart = t.encPart;
PrincipalName ticketSname = t.sname;
final ServiceCreds creds = (ServiceCreds)serviceCreds;
final KerberosPrincipal princ =
new KerberosPrincipal(ticketSname.toString());
if (creds.getName() == null) {
SecurityManager sm = System.getSecurityManager();
try {
if (sm != null) {
sm.checkPermission(new ServicePermission(
ticketSname.toString(), "accept"), acc);
}
} catch (SecurityException se) {
serviceCreds = null;
if (debug != null && Debug.isOn("handshake")) {
System.out.println("Permission to access Kerberos"
+ " secret key denied");
se.printStackTrace(System.out);
}
throw new IOException("Kerberos service not allowedy");
}
}
KerberosKey[] serverKeys = AccessController.doPrivileged(
new PrivilegedAction<KerberosKey[]>() {
@Override
public KerberosKey[] run() {
return creds.getKKeys(princ);
}
});
if (serverKeys.length == 0) {
throw new IOException("Found no key for " + princ +
(creds.getName() == null ? "" :
(", this keytab is for " + creds.getName() + " only")));
}
int encPartKeyType = encPart.getEType();
Integer encPartKeyVersion = encPart.getKeyVersionNumber();
KerberosKey dkey = null;
try {
dkey = findKey(encPartKeyType, encPartKeyVersion, serverKeys);
} catch (KrbException ke) {
throw new IOException(
"Cannot find key matching version number", ke);
}
if (dkey == null) {
throw new IOException("Cannot find key of appropriate type" +
" to decrypt ticket - need etype " + encPartKeyType);
}
EncryptionKey secretKey = new EncryptionKey(
encPartKeyType,
dkey.getEncoded());
byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET);
byte[] temp = encPart.reset(bytes);
EncTicketPart encTicketPart = new EncTicketPart(temp);
tmpPeer = new KerberosPrincipal(encTicketPart.cname.getName());
tmpLocal = new KerberosPrincipal(ticketSname.getName());
sessionKey = encTicketPart.key;
if (debug != null && Debug.isOn("handshake")) {
System.out.println("server principal: " + ticketSname);
System.out.println("cname: " + encTicketPart.cname.toString());
}
} catch (IOException e) {
throw e;
} catch (Exception e) {
if (debug != null && Debug.isOn("handshake")) {
System.out.println("KerberosWrapper error getting session key,"
+ " generating random secret (" + e.getMessage() + ")");
}
sessionKey = null;
}
if (sessionKey != null) {
preMaster = new KerberosPreMasterSecret(protocolVersion,
clientVersion, rand, encrypted, sessionKey);
} else {
preMaster = new KerberosPreMasterSecret(clientVersion, rand);
}
peerPrincipal = tmpPeer;
localPrincipal = tmpLocal;
}
private static KerberosTicket getServiceTicket(String serverName,
final AccessControlContext acc) throws IOException {
if ("localhost".equals(serverName) ||
"localhost.localdomain".equals(serverName)) {
if (debug != null && Debug.isOn("handshake")) {
System.out.println("Get the local hostname");
}
String localHost = java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<String>() {
public String run() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (java.net.UnknownHostException e) {
if (debug != null && Debug.isOn("handshake")) {
System.out.println("Warning,"
+ " cannot get the local hostname: "
+ e.getMessage());
}
return null;
}
}
});
if (localHost != null) {
serverName = localHost;
}
}
String serviceName = "host/" + serverName;
PrincipalName principal;
try {
principal = new PrincipalName(serviceName,
PrincipalName.KRB_NT_SRV_HST);
} catch (SecurityException se) {
throw se;
} catch (Exception e) {
IOException ioe = new IOException("Invalid service principal" +
" name: " + serviceName);
ioe.initCause(e);
throw ioe;
}
String realm = principal.getRealmAsString();
final String serverPrincipal = principal.toString();
final String tgsPrincipal = "krbtgt/" + realm + "@" + realm;
final String clientPrincipal = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new ServicePermission(serverPrincipal,
"initiate"), acc);
}
try {
KerberosTicket ticket = AccessController.doPrivileged(
new PrivilegedExceptionAction<KerberosTicket>() {
public KerberosTicket run() throws Exception {
return Krb5Util.getTicketFromSubjectAndTgs(
GSSCaller.CALLER_SSL_CLIENT,
clientPrincipal, serverPrincipal,
tgsPrincipal, acc);
}});
if (ticket == null) {
throw new IOException("Failed to find any kerberos service" +
" ticket for " + serverPrincipal);
}
return ticket;
} catch (PrivilegedActionException e) {
IOException ioe = new IOException(
"Attempt to obtain kerberos service ticket for " +
serverPrincipal + " failed!");
ioe.initCause(e);
throw ioe;
}
}
@Override
public SecretKey clientKeyExchange() {
byte[] secretBytes = preMaster.getUnencrypted();
return new SecretKeySpec(secretBytes, "TlsPremasterSecret");
}
@Override
public Principal getPeerPrincipal() {
return peerPrincipal;
}
@Override
public Principal getLocalPrincipal() {
return localPrincipal;
}
private static boolean versionMatches(Integer v1, int v2) {
if (v1 == null || v1 == 0 || v2 == 0) {
return true;
}
return v1.equals(v2);
}
private static KerberosKey findKey(int etype, Integer version,
KerberosKey[] keys) throws KrbException {
int ktype;
boolean etypeFound = false;
int kvno_found = 0;
KerberosKey key_found = null;
for (int i = 0; i < keys.length; i++) {
ktype = keys[i].getKeyType();
if (etype == ktype) {
int kv = keys[i].getVersionNumber();
etypeFound = true;
if (versionMatches(version, kv)) {
return keys[i];
} else if (kv > kvno_found) {
key_found = keys[i];
kvno_found = kv;
}
}
}
if ((etype == EncryptedData.ETYPE_DES_CBC_CRC ||
etype == EncryptedData.ETYPE_DES_CBC_MD5)) {
for (int i = 0; i < keys.length; i++) {
ktype = keys[i].getKeyType();
if (ktype == EncryptedData.ETYPE_DES_CBC_CRC ||
ktype == EncryptedData.ETYPE_DES_CBC_MD5) {
int kv = keys[i].getVersionNumber();
etypeFound = true;
if (versionMatches(version, kv)) {
return new KerberosKey(keys[i].getPrincipal(),
keys[i].getEncoded(),
etype,
kv);
} else if (kv > kvno_found) {
key_found = new KerberosKey(keys[i].getPrincipal(),
keys[i].getEncoded(),
etype,
kv);
kvno_found = kv;
}
}
}
}
if (etypeFound) {
return key_found;
}
return null;
}
}
}