/*
* Copyright (c) 2011, 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.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
sun.security.krb5.internal.ktab.KeyTab takeSnapshot() {
return sun.security.krb5.internal.ktab.KeyTab.getInstance(file);
}
/**
* 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</code>
*/
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</code> and the two
* <code>KeyTab</code> 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;
}
}