| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
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;  | 
 | 
    }  | 
 | 
}  |