blob: c2f8dd79478baccae14b76c19a02033ddb0a9b7c [file] [log] [blame]
/*
* 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.misc.SharedSecrets;
import sun.security.krb5.EncryptionKey;
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>
* 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>.
*
* @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;
// Set up JavaxSecurityAuthKerberosAccess in SharedSecrets
static {
SharedSecrets.setJavaxSecurityAuthKerberosAccess(
new JavaxSecurityAuthKerberosAccessImpl());
}
private KeyTab(File file) {
this.file = file;
}
/**
* Returns a {@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 {@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(file);
}
/**
* Returns the default {@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.
*/
public static KeyTab getInstance() {
return new KeyTab(null);
}
//Takes a snapshot of the keytab content
private 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.
*
* @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 {
EncryptionKey[] keys = takeSnapshot().readServiceKeys(
new PrincipalName(principal.getName()));
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() {
return file == null ? "Default keytab" : file.toString();
}
/**
* Returns a hashcode for this KeyTab.
*
* @return a hashCode() for the <code>KeyTab</code>
*/
public int hashCode() {
return Objects.hash(file);
}
/**
* 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.file, file);
}
}