jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java
author duke
Sat, 01 Dec 2007 00:00:00 +0000
changeset 2 90ce3da70b43
child 3317 a1ea2f1893f9
permissions -rw-r--r--
Initial load

/*
 * Portions Copyright 2000-2006 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

/*
 *
 *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
 *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
 */

package sun.security.krb5.internal.ktab;

import sun.security.krb5.*;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.crypto.*;
import java.util.Vector;
import java.util.ArrayList;
import java.util.Arrays;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.util.StringTokenizer;

/**
 * This class represents key table. The key table functions deal with storing
 * and retrieving service keys for use in authentication exchanges.
 *
 * @author Yanni Zhang
 */
public class KeyTab implements KeyTabConstants {
    int kt_vno;
    private static KeyTab singleton = null;
    private static final boolean DEBUG = Krb5.DEBUG;
    private static String name;
    private Vector<KeyTabEntry> entries = new Vector<KeyTabEntry> ();

    private KeyTab(String filename) throws IOException, RealmException {
        init(filename);
    }

    public static KeyTab getInstance(String s) {
        name = parse(s);
        if (name == null) {
            return getInstance();
        }
        return getInstance(new File(name));
    }

    /**
     * Gets the single instance of KeyTab class.
     * @param file the key tab file.
     * @return single instance of KeyTab;
     *  return null if error occurs while reading data out of the file.
     */
    public static KeyTab getInstance(File file) {
        try {
            if (!(file.exists())) {
                singleton = null;
            } else {
                String fname = file.getAbsolutePath();
                // Since this class deals with file I/O operations,
                // we want only one class instance existing.
                if (singleton != null) {
                    File kfile = new File(singleton.name);
                    String kname = kfile.getAbsolutePath();
                    if (kname.equalsIgnoreCase(fname)) {
                       if (DEBUG) {
                          System.out.println("KeyTab instance already exists");
                       }
                    }
                } else {
                    singleton = new KeyTab(fname);
                }
            }
        } catch (Exception e) {
            singleton = null;
            if (DEBUG) {
                System.out.println("Could not obtain an instance of KeyTab" +
                                   e.getMessage());
            }
        }
        return singleton;
    }

    /**
     * Gets the single instance of KeyTab class.
     * @return single instance of KeyTab; return null if default keytab file
     *  does not exist, or error occurs while reading data from the file.
     */
    public static KeyTab getInstance() {
        try {
            name = getDefaultKeyTab();
            if (name != null) {
                singleton = getInstance(new File(name));
            }
        } catch (Exception e) {
            singleton = null;
            if (DEBUG) {
                System.out.println("Could not obtain an instance of KeyTab" +
                                   e.getMessage());
            }
        }
        return singleton;
    }

    /**
     * The location of keytab file will be read from the configuration file
     * If it is not specified, consider user.home as the keytab file's
     * default location.
     */
    private static String getDefaultKeyTab() {
        if (name != null) {
            return name;
        } else {
            String kname = null;
            try {
                String keytab_names = Config.getInstance().getDefault
                    ("default_keytab_name", "libdefaults");
                if (keytab_names != null) {
                    StringTokenizer st = new StringTokenizer(keytab_names, " ");
                    while (st.hasMoreTokens()) {
                        kname = parse(st.nextToken());
                        if (kname != null) {
                            break;
                        }
                    }
                }
            } catch (KrbException e) {
                kname = null;
            }

            if (kname == null) {
                String user_home =
                        java.security.AccessController.doPrivileged(
                        new sun.security.action.GetPropertyAction("user.home"));

                if (user_home == null) {
                    user_home =
                        java.security.AccessController.doPrivileged(
                        new sun.security.action.GetPropertyAction("user.dir"));
                }

                if (user_home != null) {
                    kname = user_home + File.separator  + "krb5.keytab";
                }
            }
            return kname;
        }
    }

    private static String parse(String name) {
        String kname = null;
        if (name == null) {
            return null;
        }
        if ((name.length() >= 5) &&
            (name.substring(0, 5).equalsIgnoreCase("FILE:"))) {
            kname = name.substring(5);
        } else if ((name.length() >= 9) &&
                (name.substring(0, 9).equalsIgnoreCase("ANY:FILE:"))) {
            // this format found in MIT's krb5.ini.
            kname = name.substring(9);
        } else if ((name.length() >= 7) &&
                (name.substring(0, 7).equalsIgnoreCase("SRVTAB:"))) {
            // this format found in MIT's krb5.ini.
            kname = name.substring(7);
        } else
            kname = name;
        return kname;
    }

    private synchronized void init(String filename)
        throws IOException, RealmException {

        if (filename != null) {
            KeyTabInputStream kis =
                new KeyTabInputStream(new FileInputStream(filename));
            load(kis);
            kis.close();
            name = filename;
        }
    }

    private void load(KeyTabInputStream kis)
        throws IOException, RealmException {

        entries.clear();
        kt_vno = kis.readVersion();
        if (kt_vno == KRB5_KT_VNO_1) {
            kis.setNativeByteOrder();
        }
        int entryLength = 0;
        KeyTabEntry entry;
        while (kis.available() > 0) {
            entryLength = kis.readEntryLength();
            entry = kis.readEntry(entryLength, kt_vno);
            if (DEBUG) {
                System.out.println(">>> KeyTab: load() entry length: " +
                        entryLength + "; type: " +
                        (entry != null? entry.keyType : 0));
            }
            if (entry != null)
                entries.addElement(entry);
        }
    }

    /**
     * Reads the service key from the keytab file.
     * @param service the PrincipalName of the requested service.
     * @return the last service key in the keytab
     */
    public EncryptionKey readServiceKey(PrincipalName service) {
        KeyTabEntry entry = null;
        if (entries != null) {
            // Find latest entry for this service that has an etype
            // that has been configured for use
            for (int i = entries.size()-1; i >= 0; i--) {
                entry = entries.elementAt(i);
                if (entry.service.match(service)) {
                    if (EType.isSupported(entry.keyType)) {
                        return new EncryptionKey(entry.keyblock,
                                             entry.keyType,
                                             new Integer(entry.keyVersion));
                    } else if (DEBUG) {
                        System.out.println("Found unsupported keytype (" +
                            entry.keyType + ") for " + service);
                    }
                }
            }
        }
        return null;
    }

    /**
     * Reads all keys for a service from the keytab file that have
     * etypes that have been configured for use.
     * @param service the PrincipalName of the requested service
     * @return an array containing all the service keys
     */
    public EncryptionKey[] readServiceKeys(PrincipalName service) {
        KeyTabEntry entry;
        EncryptionKey key;
        int size = entries.size();
        ArrayList<EncryptionKey> keys = new ArrayList<EncryptionKey> (size);
        if (entries != null) {
            for (int i = size-1; i >= 0; i--) {
                entry = entries.elementAt(i);
                if (entry.service.match(service)) {
                    if (EType.isSupported(entry.keyType)) {
                        key = new EncryptionKey(entry.keyblock,
                                            entry.keyType,
                                            new Integer(entry.keyVersion));
                        keys.add(key);
                        if (DEBUG) {
                            System.out.println("Added key: " + entry.keyType +
                                "version: " + entry.keyVersion);
                        }
                    } else if (DEBUG) {
                        System.out.println("Found unsupported keytype (" +
                            entry.keyType + ") for " + service);
                    }
                }
            }
        }

        size = keys.size();
        if (size == 0)
            return null;
        EncryptionKey[] retVal = new EncryptionKey[size];

        // Sort keys according to default_tkt_enctypes
        int pos = 0;
        EncryptionKey k;
        if (DEBUG) {
            System.out.println("Ordering keys wrt default_tkt_enctypes list");
        }
        int[] etypes = EType.getDefaults("default_tkt_enctypes");
        if (etypes == null || etypes == EType.getBuiltInDefaults()) {
            // Either no supported types specified in default_tkt_enctypes
            // or no default_tkt_enctypes entry at all. For both cases,
            // just return supported keys in the order retrieved
            for (int i = 0; i < size; i++) {
                retVal[pos++] = keys.get(i);
            }
        } else {
            for (int j = 0; j < etypes.length && pos < size; j++) {
                int target = etypes[j];
                for (int i = 0; i < size && pos < size; i++) {
                    k = keys.get(i);
                    if (k != null && k.getEType() == target) {
                        if (DEBUG) {
                            System.out.println(pos + ": " + k);
                        }
                        retVal[pos++] = k;
                        keys.set(i, null);  // Cleared from consideration
                    }
                }
            }
            // copy the rest
            for (int i = 0; i < size && pos < size; i++) {
                k = keys.get(i);
                if (k != null) {
                    retVal[pos++] = k;
                }
            }
        }
        if (pos != size) {
            throw new RuntimeException(
                "Internal Error: did not copy all keys;expecting " + size +
                    "; got " + pos);
        }
        return retVal;
    }



    /**
     * Searches for the service entry in the keytab file.
     * The etype of the key must be one that has been configured
     * to be used.
     * @param service the PrincipalName of the requested service.
     * @return true if the entry is found, otherwise, return false.
     */
    public boolean findServiceEntry(PrincipalName service) {
        KeyTabEntry entry;
        if (entries != null) {
            for (int i = 0; i < entries.size(); i++) {
                entry = entries.elementAt(i);
                if (entry.service.match(service)) {
                    if (EType.isSupported(entry.keyType)) {
                        return true;
                    } else if (DEBUG) {
                        System.out.println("Found unsupported keytype (" +
                            entry.keyType + ") for " + service);
                    }
                }
            }
        }
        return false;
    }

    public static String tabName() {
        return name;
    }

    /**
     * Adds a new entry in the key table.
     * @param service the service which will have a new entry in the key table.
     * @param psswd the password which generates the key.
     */
    public void addEntry(PrincipalName service, char[] psswd)
         throws KrbException {

        EncryptionKey[] encKeys = EncryptionKey.acquireSecretKeys(
            psswd, service.getSalt());

        for (int i = 0; encKeys != null && i < encKeys.length; i++) {
            int keyType = encKeys[i].getEType();
            byte[] keyValue = encKeys[i].getBytes();
            int result = retrieveEntry(service, keyType);
            int kvno = 1;
            if (result != -1) {
                KeyTabEntry oldEntry = entries.elementAt(result);
                kvno = oldEntry.keyVersion;
                entries.removeElementAt(result);
                kvno += 1;
            } else
                kvno = 1;

            KeyTabEntry newEntry = new KeyTabEntry(service,
                            service.getRealm(),
                            new KerberosTime(System.currentTimeMillis()),
                                               kvno, keyType, keyValue);
            if (entries == null)
                entries = new Vector<KeyTabEntry> ();
            entries.addElement(newEntry);
        }
    }


    /**
     * Retrieves the key table entry with the specified service name.
     * @param service the service which may have an entry in the key table.
     * @return -1 if the entry is not found, else return the entry index
     * in the list.
     */
    private int retrieveEntry(PrincipalName service, int keyType) {
        int found = -1;
        KeyTabEntry e;
        if (entries != null) {
            for (int i = 0; i < entries.size(); i++) {
                e = entries.elementAt(i);
                if (service.match(e.getService()) &&
                    (keyType == -1 || e.keyType == keyType)) {
                    return i;
                }
            }
        }
        return found;
    }

    /**
     * Gets the list of service entries in key table.
     * @return array of <code>KeyTabEntry</code>.
     */
    public KeyTabEntry[] getEntries() {
        if (entries != null) {
            KeyTabEntry[] kentries = new KeyTabEntry[entries.size()];
            for (int i = 0; i < kentries.length; i++) {
                kentries[i] = entries.elementAt(i);
            }
            return kentries;
        } else {
            return null;
        }
    }

    /**
     * Creates a new default key table.
     */
    public synchronized static KeyTab create()
        throws IOException, RealmException {
        String dname = getDefaultKeyTab();
        return create(dname);
    }

    /**
     * Creates a new default key table.
     */
    public synchronized static KeyTab create(String name)
        throws IOException, RealmException {

        KeyTabOutputStream kos =
                new KeyTabOutputStream(new FileOutputStream(name));
        kos.writeVersion(KRB5_KT_VNO);
        kos.close();
        singleton = new KeyTab(name);
        return singleton;
    }

    /**
     * Saves the file at the directory.
     */
    public synchronized void save() throws IOException {
        KeyTabOutputStream kos =
                new KeyTabOutputStream(new FileOutputStream(name));
        kos.writeVersion(kt_vno);
        for (int i = 0; i < entries.size(); i++) {
            kos.writeEntry(entries.elementAt(i));
        }
        kos.close();
    }

    /**
     * Removes an entry from the key table.
     * @param service the service <code>PrincipalName</code>.
     */
    public void deleteEntry(PrincipalName service) {
        int result = retrieveEntry(service, -1);
        if (result != -1) {
            entries.removeElementAt(result);
        }
    }

    /**
     * Creates key table file version.
     * @param file the key table file.
     * @exception IOException.
     */
    public synchronized void createVersion(File file) throws IOException {
        KeyTabOutputStream kos =
                new KeyTabOutputStream(new FileOutputStream(file));
        kos.write16(KRB5_KT_VNO);
        kos.close();
    }

    public static void refresh() {
        if (singleton != null) {
            if (DEBUG) {
                System.out.println("Refreshing Keytab");
            }
            singleton = null;
        }
    }
}