/* | 
|
 * Copyright (c) 2011, 2013, 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 javax.security.auth.kerberos;  | 
|
import java.io.File;  | 
|
import java.security.AccessControlException;  | 
|
import java.util.Objects;  | 
|
import sun.security.krb5.EncryptionKey;  | 
|
import sun.security.krb5.KerberosSecrets;  | 
|
import sun.security.krb5.PrincipalName;  | 
|
import sun.security.krb5.RealmException;  | 
|
/** | 
|
 * This class encapsulates a keytab file. | 
|
 * <p> | 
|
 * A Kerberos JAAS login module that obtains long term secret keys from a | 
|
 * keytab file should use this class. The login module will store | 
|
 * an instance of this class in the private credential set of a | 
|
 * {@link javax.security.auth.Subject Subject} during the commit phase of the | 
|
 * authentication process. | 
|
 * <p> | 
|
 * If a {@code KeyTab} object is obtained from {@link #getUnboundInstance()} | 
|
 * or {@link #getUnboundInstance(java.io.File)}, it is unbound and thus can be | 
|
 * used by any service principal. Otherwise, if it's obtained from | 
|
 * {@link #getInstance(KerberosPrincipal)} or | 
|
 * {@link #getInstance(KerberosPrincipal, java.io.File)}, it is bound to the | 
|
 * specific service principal and can only be used by it. | 
|
 * <p> | 
|
 * Please note the constructors {@link #getInstance()} and | 
|
 * {@link #getInstance(java.io.File)} were created when there was no support | 
|
 * for unbound keytabs. These methods should not be used anymore. An object | 
|
 * created with either of these methods are considered to be bound to an | 
|
 * unknown principal, which means, its {@link #isBound()} returns true and | 
|
 * {@link #getPrincipal()} returns null. | 
|
 * <p> | 
|
 * It might be necessary for the application to be granted a | 
|
 * {@link javax.security.auth.PrivateCredentialPermission | 
|
 * PrivateCredentialPermission} if it needs to access the KeyTab | 
|
 * instance from a Subject. This permission is not needed when the | 
|
 * application depends on the default JGSS Kerberos mechanism to access the | 
|
 * KeyTab. In that case, however, the application will need an appropriate | 
|
 * {@link javax.security.auth.kerberos.ServicePermission ServicePermission}. | 
|
 * <p> | 
|
 * The keytab file format is described at | 
|
 * <a href="http://www.ioplex.com/utilities/keytab.txt"> | 
|
 * http://www.ioplex.com/utilities/keytab.txt</a>. | 
|
 * <p> | 
|
 * @since 1.7 | 
|
*/  | 
|
public final class KeyTab { | 
|
/*  | 
|
* Impl notes:  | 
|
*  | 
|
* This class is only a name, a permanent link to the keytab source  | 
|
* (can be missing). Itself has no content. In order to read content,  | 
|
* take a snapshot and read from it.  | 
|
*  | 
|
* The snapshot is of type sun.security.krb5.internal.ktab.KeyTab, which  | 
|
* contains the content of the keytab file when the snapshot is taken.  | 
|
* Itself has no refresh function and mostly an immutable class (except  | 
|
* for the create/add/save methods only used by the ktab command).  | 
|
*/  | 
|
// Source, null if using the default one. Note that the default name  | 
|
    // is maintained in snapshot, this field is never "resolved". | 
|
private final File file;  | 
|
// Bound user: normally from the "principal" value in a JAAS krb5  | 
|
    // login conf. Will be null if it's "*". | 
|
private final KerberosPrincipal princ;  | 
|
private final boolean bound;  | 
|
    // Set up JavaxSecurityAuthKerberosAccess in KerberosSecrets | 
|
    static { | 
|
KerberosSecrets.setJavaxSecurityAuthKerberosAccess(  | 
|
new JavaxSecurityAuthKerberosAccessImpl());  | 
|
}  | 
|
private KeyTab(KerberosPrincipal princ, File file, boolean bound) {  | 
|
this.princ = princ;  | 
|
this.file = file;  | 
|
this.bound = bound;  | 
|
}  | 
|
    /** | 
|
     * Returns a {@code KeyTab} instance from a {@code File} object | 
|
     * that is bound to an unknown service principal. | 
|
     * <p> | 
|
     * The result of this method is never null. This method only associates | 
|
     * the returned {@code KeyTab} object with the file and does not read it. | 
|
     * <p> | 
|
     * Developers should call {@link #getInstance(KerberosPrincipal,File)} | 
|
     * when the bound service principal is known. | 
|
     * @param file the keytab {@code File} object, must not be null | 
|
     * @return the keytab instance | 
|
     * @throws NullPointerException if the {@code file} argument is null | 
|
*/  | 
|
public static KeyTab getInstance(File file) {  | 
|
if (file == null) {  | 
|
throw new NullPointerException("file must be non null");  | 
|
}  | 
|
return new KeyTab(null, file, true);  | 
|
}  | 
|
    /** | 
|
     * Returns an unbound {@code KeyTab} instance from a {@code File} | 
|
     * object. | 
|
     * <p> | 
|
     * The result of this method is never null. This method only associates | 
|
     * the returned {@code KeyTab} object with the file and does not read it. | 
|
     * @param file the keytab {@code File} object, must not be null | 
|
     * @return the keytab instance | 
|
     * @throws NullPointerException if the file argument is null | 
|
     * @since 1.8 | 
|
*/  | 
|
public static KeyTab getUnboundInstance(File file) {  | 
|
if (file == null) {  | 
|
throw new NullPointerException("file must be non null");  | 
|
}  | 
|
return new KeyTab(null, file, false);  | 
|
}  | 
|
    /** | 
|
     * Returns a {@code KeyTab} instance from a {@code File} object | 
|
     * that is bound to the specified service principal. | 
|
     * <p> | 
|
     * The result of this method is never null. This method only associates | 
|
     * the returned {@code KeyTab} object with the file and does not read it. | 
|
     * @param princ the bound service principal, must not be null | 
|
     * @param file the keytab {@code File} object, must not be null | 
|
     * @return the keytab instance | 
|
     * @throws NullPointerException if either of the arguments is null | 
|
     * @since 1.8 | 
|
*/  | 
|
public static KeyTab getInstance(KerberosPrincipal princ, File file) {  | 
|
if (princ == null) {  | 
|
throw new NullPointerException("princ must be non null");  | 
|
}  | 
|
if (file == null) {  | 
|
throw new NullPointerException("file must be non null");  | 
|
}  | 
|
return new KeyTab(princ, file, true);  | 
|
}  | 
|
    /** | 
|
     * Returns the default {@code KeyTab} instance that is bound | 
|
     * to an unknown service principal. | 
|
     * <p> | 
|
     * The result of this method is never null. This method only associates | 
|
     * the returned {@code KeyTab} object with the default keytab file and | 
|
     * does not read it. | 
|
     * <p> | 
|
     * Developers should call {@link #getInstance(KerberosPrincipal)} | 
|
     * when the bound service principal is known. | 
|
     * @return the default keytab instance. | 
|
*/  | 
|
public static KeyTab getInstance() {  | 
|
return new KeyTab(null, null, true);  | 
|
}  | 
|
    /** | 
|
     * Returns the default unbound {@code KeyTab} instance. | 
|
     * <p> | 
|
     * The result of this method is never null. This method only associates | 
|
     * the returned {@code KeyTab} object with the default keytab file and | 
|
     * does not read it. | 
|
     * @return the default keytab instance | 
|
     * @since 1.8 | 
|
*/  | 
|
public static KeyTab getUnboundInstance() {  | 
|
return new KeyTab(null, null, false);  | 
|
}  | 
|
    /** | 
|
     * Returns the default {@code KeyTab} instance that is bound | 
|
     * to the specified service principal. | 
|
     * <p> | 
|
     * The result of this method is never null. This method only associates | 
|
     * the returned {@code KeyTab} object with the default keytab file and | 
|
     * does not read it. | 
|
     * @param princ the bound service principal, must not be null | 
|
     * @return the default keytab instance | 
|
     * @throws NullPointerException if {@code princ} is null | 
|
     * @since 1.8 | 
|
*/  | 
|
public static KeyTab getInstance(KerberosPrincipal princ) {  | 
|
if (princ == null) {  | 
|
throw new NullPointerException("princ must be non null");  | 
|
}  | 
|
return new KeyTab(princ, null, true);  | 
|
}  | 
|
// Takes a snapshot of the keytab content. This method is called by  | 
|
    // JavaxSecurityAuthKerberosAccessImpl so no more private | 
|
sun.security.krb5.internal.ktab.KeyTab takeSnapshot() {  | 
|
        try { | 
|
return sun.security.krb5.internal.ktab.KeyTab.getInstance(file);  | 
|
} catch (AccessControlException ace) {  | 
|
if (file != null) {  | 
|
                // It's OK to show the name if caller specified it | 
|
throw ace;  | 
|
            } else { | 
|
AccessControlException ace2 = new AccessControlException(  | 
|
                        "Access to default keytab denied (modified exception)"); | 
|
ace2.setStackTrace(ace.getStackTrace());  | 
|
throw ace2;  | 
|
}  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Returns fresh keys for the given Kerberos principal. | 
|
     * <p> | 
|
     * Implementation of this method should make sure the returned keys match | 
|
     * the latest content of the keytab file. The result is a newly created | 
|
     * copy that can be modified by the caller without modifying the keytab | 
|
     * object. The caller should {@link KerberosKey#destroy() destroy} the | 
|
     * result keys after they are used. | 
|
     * <p> | 
|
     * Please note that the keytab file can be created after the | 
|
     * {@code KeyTab} object is instantiated and its content may change over | 
|
     * time. Therefore, an application should call this method only when it | 
|
     * needs to use the keys. Any previous result from an earlier invocation | 
|
     * could potentially be expired. | 
|
     * <p> | 
|
     * If there is any error (say, I/O error or format error) | 
|
     * during the reading process of the KeyTab file, a saved result should be | 
|
     * returned. If there is no saved result (say, this is the first time this | 
|
     * method is called, or, all previous read attempts failed), an empty array | 
|
     * should be returned. This can make sure the result is not drastically | 
|
     * changed during the (probably slow) update of the keytab file. | 
|
     * <p> | 
|
     * Each time this method is called and the reading of the file succeeds | 
|
     * with no exception (say, I/O error or file format error), | 
|
     * the result should be saved for {@code principal}. The implementation can | 
|
     * also save keys for other principals having keys in the same keytab object | 
|
     * if convenient. | 
|
     * <p> | 
|
     * Any unsupported key read from the keytab is ignored and not included | 
|
     * in the result. | 
|
     * <p> | 
|
     * If this keytab is bound to a specific principal, calling this method on | 
|
     * another principal will return an empty array. | 
|
     * | 
|
     * @param principal the Kerberos principal, must not be null. | 
|
     * @return the keys (never null, may be empty) | 
|
     * @throws NullPointerException if the {@code principal} | 
|
     * argument is null | 
|
     * @throws SecurityException if a security manager exists and the read | 
|
     * access to the keytab file is not permitted | 
|
*/  | 
|
public KerberosKey[] getKeys(KerberosPrincipal principal) {  | 
|
        try { | 
|
if (princ != null && !principal.equals(princ)) {  | 
|
return new KerberosKey[0];  | 
|
}  | 
|
PrincipalName pn = new PrincipalName(principal.getName());  | 
|
EncryptionKey[] keys = takeSnapshot().readServiceKeys(pn);  | 
|
KerberosKey[] kks = new KerberosKey[keys.length];  | 
|
for (int i=0; i<kks.length; i++) {  | 
|
Integer tmp = keys[i].getKeyVersionNumber();  | 
|
kks[i] = new KerberosKey(  | 
|
principal,  | 
|
keys[i].getBytes(),  | 
|
keys[i].getEType(),  | 
|
tmp == null ? 0 : tmp.intValue());  | 
|
keys[i].destroy();  | 
|
}  | 
|
return kks;  | 
|
} catch (RealmException re) {  | 
|
return new KerberosKey[0];  | 
|
}  | 
|
}  | 
|
EncryptionKey[] getEncryptionKeys(PrincipalName principal) {  | 
|
return takeSnapshot().readServiceKeys(principal);  | 
|
}  | 
|
    /** | 
|
     * Checks if the keytab file exists. Implementation of this method | 
|
     * should make sure that the result matches the latest status of the | 
|
     * keytab file. | 
|
     * <p> | 
|
     * The caller can use the result to determine if it should fallback to | 
|
     * another mechanism to read the keys. | 
|
     * @return true if the keytab file exists; false otherwise. | 
|
     * @throws SecurityException if a security manager exists and the read | 
|
     * access to the keytab file is not permitted | 
|
*/  | 
|
    public boolean exists() { | 
|
return !takeSnapshot().isMissing();  | 
|
}  | 
|
public String toString() {  | 
|
String s = (file == null) ? "Default keytab" : file.toString();  | 
|
if (!bound) return s;  | 
|
else if (princ == null) return s + " for someone";  | 
|
else return s + " for " + princ;  | 
|
}  | 
|
    /** | 
|
     * Returns a hashcode for this KeyTab. | 
|
     * | 
|
     * @return a hashCode() for the {@code KeyTab} | 
|
*/  | 
|
    public int hashCode() { | 
|
return Objects.hash(file, princ, bound);  | 
|
}  | 
|
    /** | 
|
     * Compares the specified Object with this KeyTab for equality. | 
|
     * Returns true if the given object is also a | 
|
     * {@code KeyTab} and the two | 
|
     * {@code KeyTab} instances are equivalent. | 
|
     * | 
|
     * @param other the Object to compare to | 
|
     * @return true if the specified object is equal to this KeyTab | 
|
*/  | 
|
public boolean equals(Object other) {  | 
|
if (other == this)  | 
|
return true;  | 
|
if (! (other instanceof KeyTab)) {  | 
|
return false;  | 
|
}  | 
|
KeyTab otherKtab = (KeyTab) other;  | 
|
return Objects.equals(otherKtab.princ, princ) &&  | 
|
Objects.equals(otherKtab.file, file) &&  | 
|
bound == otherKtab.bound;  | 
|
}  | 
|
    /** | 
|
     * Returns the service principal this {@code KeyTab} object | 
|
     * is bound to. Returns {@code null} if it's not bound. | 
|
     * <p> | 
|
     * Please note the deprecated constructors create a KeyTab object bound for | 
|
     * some unknown principal. In this case, this method also returns null. | 
|
     * User can call {@link #isBound()} to verify this case. | 
|
     * @return the service principal | 
|
     * @since 1.8 | 
|
*/  | 
|
public KerberosPrincipal getPrincipal() {  | 
|
return princ;  | 
|
}  | 
|
    /** | 
|
     * Returns if the keytab is bound to a principal | 
|
     * @return if the keytab is bound to a principal | 
|
     * @since 1.8 | 
|
*/  | 
|
    public boolean isBound() { | 
|
return bound;  | 
|
}  | 
|
}  |