|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl.krb5; |
|
|
|
import java.io.IOException; |
|
import java.net.InetAddress; |
|
import java.net.UnknownHostException; |
|
import java.security.AccessController; |
|
import java.security.AccessControlContext; |
|
import java.security.PrivilegedExceptionAction; |
|
import java.security.PrivilegedActionException; |
|
import java.util.Arrays; |
|
import java.security.PrivilegedAction; |
|
|
|
import javax.security.auth.kerberos.KerberosTicket; |
|
import javax.net.ssl.SSLKeyException; |
|
import javax.security.auth.kerberos.KerberosKey; |
|
import javax.security.auth.kerberos.KerberosPrincipal; |
|
import javax.security.auth.kerberos.ServicePermission; |
|
import sun.security.jgss.GSSCaller; |
|
|
|
import sun.security.krb5.EncryptionKey; |
|
import sun.security.krb5.EncryptedData; |
|
import sun.security.krb5.PrincipalName; |
|
import sun.security.krb5.internal.Ticket; |
|
import sun.security.krb5.internal.EncTicketPart; |
|
import sun.security.krb5.internal.crypto.KeyUsage; |
|
|
|
import sun.security.jgss.krb5.Krb5Util; |
|
import sun.security.jgss.krb5.ServiceCreds; |
|
import sun.security.krb5.KrbException; |
|
|
|
import sun.security.ssl.Krb5Helper; |
|
import sun.security.ssl.SSLLogger; |
|
|
|
public final class KrbClientKeyExchangeHelperImpl |
|
implements sun.security.ssl.KrbClientKeyExchangeHelper { |
|
|
|
private byte[] preMaster; |
|
private byte[] preMasterEnc; |
|
private byte[] encodedTicket; |
|
private KerberosPrincipal peerPrincipal; |
|
private KerberosPrincipal localPrincipal; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public void init(byte[] preMaster, String serverName, |
|
AccessControlContext acc) throws IOException { |
|
this.preMaster = preMaster; |
|
|
|
|
|
KerberosTicket ticket = getServiceTicket(serverName, acc); |
|
encodedTicket = ticket.getEncoded(); |
|
|
|
|
|
peerPrincipal = ticket.getServer(); |
|
localPrincipal = ticket.getClient(); |
|
|
|
|
|
EncryptionKey sessionKey = new EncryptionKey( |
|
ticket.getSessionKeyType(), |
|
ticket.getSessionKey().getEncoded()); |
|
encryptPremasterSecret(sessionKey); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public void init(byte[] encodedTicket, byte[] preMasterEnc, |
|
Object serviceCreds, AccessControlContext acc) throws IOException { |
|
this.encodedTicket = encodedTicket; |
|
this.preMasterEnc = preMasterEnc; |
|
EncryptionKey sessionKey = 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(Krb5Helper.getServicePermission( |
|
ticketSname.toString(), "accept"), acc); |
|
} |
|
} catch (SecurityException se) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Permission to access Kerberos" |
|
+ " secret key denied"); |
|
} |
|
throw new IOException("Kerberos service not allowed"); |
|
} |
|
} |
|
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"))); |
|
} |
|
|
|
// See if we have the right key to decrypt the ticket to get |
|
|
|
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); |
|
|
|
|
|
peerPrincipal = |
|
new KerberosPrincipal(encTicketPart.cname.getName()); |
|
localPrincipal = new KerberosPrincipal(ticketSname.getName()); |
|
|
|
sessionKey = encTicketPart.key; |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("server principal: " + ticketSname); |
|
SSLLogger.fine("cname: " + encTicketPart.cname.toString()); |
|
} |
|
} catch (Exception e) { |
|
sessionKey = null; |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Error getting the Kerberos session key" + |
|
" to decrypt the pre-master secret"); |
|
} |
|
} |
|
if (sessionKey != null) |
|
decryptPremasterSecret(sessionKey); |
|
} |
|
|
|
@Override |
|
public byte[] getEncodedTicket() { |
|
return encodedTicket; |
|
} |
|
|
|
@Override |
|
public byte[] getEncryptedPreMasterSecret() { |
|
return preMasterEnc; |
|
} |
|
|
|
@Override |
|
public byte[] getPlainPreMasterSecret() { |
|
return preMaster; |
|
} |
|
|
|
@Override |
|
public KerberosPrincipal getPeerPrincipal() { |
|
return peerPrincipal; |
|
} |
|
|
|
@Override |
|
public KerberosPrincipal getLocalPrincipal() { |
|
return localPrincipal; |
|
} |
|
|
|
private void encryptPremasterSecret(EncryptionKey sessionKey) |
|
throws IOException { |
|
if (sessionKey.getEType() == |
|
EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) { |
|
throw new IOException( |
|
"session keys with des3-cbc-hmac-sha1-kd encryption type " + |
|
"are not supported for TLS Kerberos cipher suites"); |
|
} |
|
try { |
|
EncryptedData eData = new EncryptedData(sessionKey, preMaster, |
|
KeyUsage.KU_UNKNOWN); |
|
preMasterEnc = eData.getBytes(); |
|
} catch (KrbException e) { |
|
throw (IOException) new SSLKeyException("Kerberos pre-master" + |
|
" secret error").initCause(e); |
|
} |
|
} |
|
|
|
private void decryptPremasterSecret(EncryptionKey sessionKey) |
|
throws IOException { |
|
if (sessionKey.getEType() == |
|
EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) { |
|
throw new IOException( |
|
"session keys with des3-cbc-hmac-sha1-kd encryption type " + |
|
"are not supported for TLS Kerberos cipher suites"); |
|
} |
|
try { |
|
EncryptedData data = new EncryptedData(sessionKey.getEType(), |
|
null , preMasterEnc); |
|
byte[] temp = data.decrypt(sessionKey, KeyUsage.KU_UNKNOWN); |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
if (preMasterEnc != null) { |
|
SSLLogger.fine("decrypted premaster secret", temp); |
|
} |
|
} |
|
|
|
// Remove padding bytes after decryption. Only DES and DES3 have |
|
|
|
if (temp.length == 52 && |
|
data.getEType() == EncryptedData.ETYPE_DES_CBC_CRC) { |
|
|
|
if (paddingByteIs(temp, 52, (byte)4) || |
|
paddingByteIs(temp, 52, (byte)0)) { |
|
temp = Arrays.copyOf(temp, 48); |
|
} |
|
} else if (temp.length == 56 && |
|
data.getEType() == EncryptedData.ETYPE_DES_CBC_MD5) { |
|
|
|
if (paddingByteIs(temp, 56, (byte)8)) { |
|
temp = Arrays.copyOf(temp, 48); |
|
} |
|
} |
|
|
|
preMaster = temp; |
|
} catch (Exception e) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Error decrypting the pre-master secret"); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean paddingByteIs(byte[] data, int len, byte b) { |
|
for (int i=48; i<len; i++) { |
|
if (data[i] != b) return false; |
|
} |
|
return true; |
|
} |
|
|
|
|
|
private static KerberosTicket getServiceTicket(String serverName, |
|
final AccessControlContext acc) throws IOException { |
|
|
|
if ("localhost".equals(serverName) || |
|
"localhost.localdomain".equals(serverName)) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Get the local hostname"); |
|
} |
|
String localHost = java.security.AccessController.doPrivileged( |
|
new java.security.PrivilegedAction<String>() { |
|
public String run() { |
|
try { |
|
return InetAddress.getLocalHost().getHostName(); |
|
} catch (UnknownHostException e) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Warning," |
|
+ " cannot get the local hostname: " |
|
+ e.getMessage()); |
|
} |
|
return null; |
|
} |
|
} |
|
}); |
|
if (localHost != null) { |
|
serverName = localHost; |
|
} |
|
} |
|
|
|
// Resolve serverName (possibly in IP addr form) to Kerberos principal |
|
|
|
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; |
|
|
|
|
|
// check permission to obtain a service ticket to initiate a |
|
|
|
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; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
|
|
// When no matched kvno is found, returns tke key of the same |
|
|
|
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; |
|
} |
|
} |
|
} |
|
// Key not found. |
|
|
|
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; |
|
} |
|
} |