419 lines · 16.0 KB · Java
RawDownload
/* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.ssl.krb5; import java.io.IOException;import java.io.PrintStream;import java.security.AccessController;import java.security.AccessControlContext;import java.security.PrivilegedExceptionAction;import java.security.PrivilegedActionException;import java.security.SecureRandom;import java.net.InetAddress; import javax.crypto.SecretKey;import javax.security.auth.kerberos.KerberosTicket;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.Realm;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.krb5.KrbException;import sun.security.krb5.internal.Krb5; import sun.security.ssl.Debug;import sun.security.ssl.HandshakeInStream;import sun.security.ssl.HandshakeOutStream;import sun.security.ssl.ProtocolVersion; /** * This is Kerberos option in the client key exchange message * (CLIENT -> SERVER). It holds the Kerberos ticket and the encrypted * premaster secret encrypted with the session key sealed in the ticket. * From RFC 2712: *  struct *  { *    opaque Ticket; *    opaque authenticator;            // optional *    opaque EncryptedPreMasterSecret; // encrypted with the session key *                                     // which is sealed in the ticket *  } KerberosWrapper; * * * Ticket and authenticator are encrypted as per RFC 1510 (in ASN.1) * Encrypted pre-master secret has the same structure as it does for RSA * except for Kerberos, the encryption key is the session key instead of * the RSA public key. * * XXX authenticator currently ignored * */public final class KerberosClientKeyExchangeImpl    extends sun.security.ssl.KerberosClientKeyExchange {     private KerberosPreMasterSecret preMaster;    private byte[] encodedTicket;    private KerberosPrincipal peerPrincipal;    private KerberosPrincipal localPrincipal;     public KerberosClientKeyExchangeImpl() {    }     /**     * Creates an instance of KerberosClientKeyExchange consisting of the     * Kerberos service ticket, authenticator and encrypted premaster secret.     * Called by client handshaker.     *     * @param serverName name of server with which to do handshake;     *             this is used to get the Kerberos service ticket     * @param protocolVersion Maximum version supported by client (i.e,     *          version it requested in client hello)     * @param rand random number generator to use for generating pre-master     *          secret     */    @Override    public void init(String serverName, boolean isLoopback,        AccessControlContext acc, ProtocolVersion protocolVersion,        SecureRandom rand) throws IOException {          // Get service ticket         KerberosTicket ticket = getServiceTicket(serverName, isLoopback, acc);         encodedTicket = ticket.getEncoded();          // Record the Kerberos principals         peerPrincipal = ticket.getServer();         localPrincipal = ticket.getClient();          // Optional authenticator, encrypted using session key,         // currently ignored          // Generate premaster secret and encrypt it using session key         EncryptionKey sessionKey = new EncryptionKey(                                        ticket.getSessionKeyType(),                                        ticket.getSessionKey().getEncoded());          preMaster = new KerberosPreMasterSecret(protocolVersion,             rand, sessionKey);    }     /**     * Creates an instance of KerberosClientKeyExchange from its ASN.1 encoding.     * Used by ServerHandshaker to verify and obtain premaster secret.     *     * @param protocolVersion current protocol version     * @param clientVersion version requested by client in its ClientHello;     *          used by premaster secret version check     * @param rand random number generator used for generating random     *          premaster secret if ticket and/or premaster verification fails     * @param input inputstream from which to get ASN.1-encoded KerberosWrapper     * @param serverKey server's master secret key     */    @Override    public void init(ProtocolVersion protocolVersion,        ProtocolVersion clientVersion,        SecureRandom rand, HandshakeInStream input, SecretKey[] secretKeys)        throws IOException {         KerberosKey[] serverKeys = (KerberosKey[])secretKeys;         // Read ticket        encodedTicket = input.getBytes16();         if (debug != null && Debug.isOn("verbose")) {            Debug.println(System.out,                "encoded Kerberos service ticket", encodedTicket);        }         EncryptionKey sessionKey = null;         try {            Ticket t = new Ticket(encodedTicket);             EncryptedData encPart = t.encPart;            PrincipalName ticketSname = t.sname;            Realm ticketRealm = t.realm;             String serverPrincipal = serverKeys[0].getPrincipal().getName();             /*             * permission to access and use the secret key of the Kerberized             * "host" service is done in ServerHandshaker.getKerberosKeys()             * to ensure server has the permission to use the secret key             * before promising the client             */             // Check that ticket Sname matches serverPrincipal            String ticketPrinc = ticketSname.toString().concat("@" +                                        ticketRealm.toString());            if (!ticketPrinc.equals(serverPrincipal)) {                if (debug != null && Debug.isOn("handshake"))                   System.out.println("Service principal in Ticket does not"                        + " match associated principal in KerberosKey");                throw new IOException("Server principal is " +                    serverPrincipal + " but ticket is for " +                    ticketPrinc);            }             // See if we have the right key to decrypt the ticket to get            // the session key.            int encPartKeyType = encPart.getEType();            Integer encPartKeyVersion = encPart.getKeyVersionNumber();            KerberosKey dkey = null;            try {                dkey = findKey(encPartKeyType, encPartKeyVersion, serverKeys);            } catch (KrbException ke) { // a kvno mismatch                throw new IOException(                        "Cannot find key matching version number", ke);            }            if (dkey == null) {                // %%% Should print string repr of etype                throw new IOException(        "Cannot find key of appropriate type to decrypt ticket - need etype " +                                   encPartKeyType);            }             EncryptionKey secretKey = new EncryptionKey(                encPartKeyType,                dkey.getEncoded());             // Decrypt encPart using server's secret key            byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET);             // Reset data stream after decryption, remove redundant bytes            byte[] temp = encPart.reset(bytes);            EncTicketPart encTicketPart = new EncTicketPart(temp);             // Record the Kerberos Principals            peerPrincipal =                new KerberosPrincipal(encTicketPart.cname.getName());            localPrincipal = new KerberosPrincipal(ticketSname.getName());             sessionKey = encTicketPart.key;             if (debug != null && Debug.isOn("handshake")) {                System.out.println("server principal: " + serverPrincipal);                System.out.println("realm: " + encTicketPart.crealm.toString());                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;        }         input.getBytes16();   // XXX Read and ignore authenticator         if (sessionKey != null) {            preMaster = new KerberosPreMasterSecret(protocolVersion,                clientVersion, rand, input, sessionKey);        } else {            // Generate bogus premaster secret            preMaster = new KerberosPreMasterSecret(clientVersion, rand);        }    }     @Override    public int messageLength() {        return (6 + encodedTicket.length + preMaster.getEncrypted().length);    }     @Override    public void send(HandshakeOutStream s) throws IOException {        s.putBytes16(encodedTicket);        s.putBytes16(null); // XXX no authenticator        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());        }    }     // Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context    private static KerberosTicket getServiceTicket(String srvName,        boolean isLoopback, final AccessControlContext acc) throws IOException {         // get the local hostname if srvName is loopback address        String serverName = srvName;        if (isLoopback) {            String localHost = java.security.AccessController.doPrivileged(                new java.security.PrivilegedAction<String>() {                public String run() {                    String hostname;                    try {                        hostname = InetAddress.getLocalHost().getHostName();                    } catch (java.net.UnknownHostException e) {                        hostname = "localhost";                    }                    return hostname;                }            });          serverName = localHost;        }         // Resolve serverName (possibly in IP addr form) to Kerberos principal        // name for service with hostname        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;  // use default          // check permission to obtain a service ticket to initiate a        // context with the "host" service        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 byte[] getUnencryptedPreMasterSecret() {        return preMaster.getUnencrypted();    }     @Override    public KerberosPrincipal getPeerPrincipal() {        return peerPrincipal;    }     @Override    public KerberosPrincipal getLocalPrincipal() {        return localPrincipal;    }     /**     * Determines if a kvno matches another kvno. Used in the method     * findKey(etype, version, keys). Always returns true if either input     * is null or zero, in case any side does not have kvno info available.     *     * Note: zero is included because N/A is not a legal value for kvno     * in javax.security.auth.kerberos.KerberosKey. Therefore, the info     * that the kvno is N/A might be lost when converting between     * EncryptionKey and KerberosKey.     */    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;        for (int i = 0; i < keys.length; i++) {            ktype = keys[i].getKeyType();            if (etype == ktype) {                etypeFound = true;                if (versionMatches(version, keys[i].getVersionNumber())) {                    return keys[i];                }            }        }        // Key not found.        // %%% kludge to allow DES keys to be used for diff etypes        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) {                    etypeFound = true;                    if (versionMatches(version, keys[i].getVersionNumber())) {                        return new KerberosKey(keys[i].getPrincipal(),                            keys[i].getEncoded(),                            etype,                            keys[i].getVersionNumber());                    }                }            }        }        if (etypeFound) {            throw new KrbException(Krb5.KRB_AP_ERR_BADKEYVER);        }        return null;    }}