263 lines
9.5 KiB
Java
263 lines
9.5 KiB
Java
/*
|
|
* Copyright (c) 2012, 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 sun.security.jgss.krb5;
|
|
|
|
import javax.security.auth.kerberos.KerberosTicket;
|
|
import javax.security.auth.kerberos.KerberosKey;
|
|
import javax.security.auth.kerberos.KerberosPrincipal;
|
|
import javax.security.auth.kerberos.KeyTab;
|
|
import javax.security.auth.Subject;
|
|
|
|
import sun.security.krb5.Credentials;
|
|
import sun.security.krb5.EncryptionKey;
|
|
import sun.security.krb5.KrbException;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import sun.security.krb5.*;
|
|
import sun.security.krb5.internal.Krb5;
|
|
|
|
/**
|
|
* Credentials of a kerberos acceptor. A KerberosPrincipal object (kp) is
|
|
* the principal. It can be specified as the serverPrincipal argument
|
|
* in the getInstance() method, or uses only KerberosPrincipal in the subject.
|
|
* Otherwise, the creds object is unbound and kp is null.
|
|
*
|
|
* The class also encapsulates various secrets, which can be:
|
|
*
|
|
* 1. Some KerberosKeys (generated from password)
|
|
* 2. Some KeyTabs (for a typical service based on keytabs)
|
|
* 3. A TGT (for S4U2proxy extension or user2user)
|
|
*
|
|
* Note that some secrets can coexist. For example, a user2user service
|
|
* can use its keytab (or keys) if the client can successfully obtain a
|
|
* normal service ticket, or it can use the TGT (actually, the session key
|
|
* of the TGT) if the client can only acquire a service ticket
|
|
* of ENC-TKT-IN-SKEY style.
|
|
*
|
|
* @since 1.8
|
|
*/
|
|
public final class ServiceCreds {
|
|
// The principal, or null if unbound
|
|
private KerberosPrincipal kp;
|
|
|
|
// All principals in the subject's princ set
|
|
private Set<KerberosPrincipal> allPrincs;
|
|
|
|
// All private credentials that can be used
|
|
private List<KeyTab> ktabs;
|
|
private List<KerberosKey> kk;
|
|
private KerberosTicket tgt;
|
|
|
|
private boolean destroyed;
|
|
|
|
private ServiceCreds() {
|
|
// Make sure this class cannot be instantiated externally.
|
|
}
|
|
|
|
/**
|
|
* Creates a ServiceCreds object based on info in a Subject for
|
|
* a given principal name (if specified).
|
|
* @return the object, or null if there is no private creds for it
|
|
*/
|
|
public static ServiceCreds getInstance(
|
|
Subject subj, String serverPrincipal) {
|
|
|
|
ServiceCreds sc = new ServiceCreds();
|
|
|
|
sc.allPrincs =
|
|
subj.getPrincipals(KerberosPrincipal.class);
|
|
|
|
// Compatibility. A key implies its own principal
|
|
for (KerberosKey key: SubjectComber.findMany(
|
|
subj, serverPrincipal, null, KerberosKey.class)) {
|
|
sc.allPrincs.add(key.getPrincipal());
|
|
}
|
|
|
|
if (serverPrincipal != null) { // A named principal
|
|
sc.kp = new KerberosPrincipal(serverPrincipal);
|
|
} else {
|
|
// For compatibility reason, we set the name of default principal
|
|
// to the "only possible" name it can take, which means there is
|
|
// only one KerberosPrincipal and there is no unbound keytabs
|
|
if (sc.allPrincs.size() == 1) {
|
|
boolean hasUnbound = false;
|
|
for (KeyTab ktab: SubjectComber.findMany(
|
|
subj, null, null, KeyTab.class)) {
|
|
if (!ktab.isBound()) {
|
|
hasUnbound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasUnbound) {
|
|
sc.kp = sc.allPrincs.iterator().next();
|
|
serverPrincipal = sc.kp.getName();
|
|
}
|
|
}
|
|
}
|
|
|
|
sc.ktabs = SubjectComber.findMany(
|
|
subj, serverPrincipal, null, KeyTab.class);
|
|
sc.kk = SubjectComber.findMany(
|
|
subj, serverPrincipal, null, KerberosKey.class);
|
|
sc.tgt = SubjectComber.find(
|
|
subj, null, serverPrincipal, KerberosTicket.class);
|
|
if (sc.ktabs.isEmpty() && sc.kk.isEmpty() && sc.tgt == null) {
|
|
return null;
|
|
}
|
|
|
|
sc.destroyed = false;
|
|
|
|
return sc;
|
|
}
|
|
|
|
// can be null
|
|
public String getName() {
|
|
if (destroyed) {
|
|
throw new IllegalStateException("This object is destroyed");
|
|
}
|
|
return kp == null ? null : kp.getName();
|
|
}
|
|
|
|
/**
|
|
* Gets keys for "someone". Used in 2 cases:
|
|
* 1. By TLS because it needs to get keys before client comes in.
|
|
* 2. As a fallback in getEKeys() below.
|
|
* This method can still return an empty array.
|
|
*/
|
|
public KerberosKey[] getKKeys() {
|
|
if (destroyed) {
|
|
throw new IllegalStateException("This object is destroyed");
|
|
}
|
|
KerberosPrincipal one = kp; // named principal
|
|
if (one == null && !allPrincs.isEmpty()) { // or, a known principal
|
|
one = allPrincs.iterator().next();
|
|
}
|
|
if (one == null) { // Or, some random one
|
|
for (KeyTab ktab: ktabs) {
|
|
// Must be unbound keytab, otherwise, allPrincs is not empty
|
|
PrincipalName pn =
|
|
Krb5Util.snapshotFromJavaxKeyTab(ktab).getOneName();
|
|
if (pn != null) {
|
|
one = new KerberosPrincipal(pn.getName());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (one != null) {
|
|
return getKKeys(one);
|
|
} else {
|
|
return new KerberosKey[0];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get kkeys for a principal,
|
|
* @param princ the target name initiator requests. Not null.
|
|
* @return keys for the princ, never null, might be empty
|
|
*/
|
|
public KerberosKey[] getKKeys(KerberosPrincipal princ) {
|
|
if (destroyed) {
|
|
throw new IllegalStateException("This object is destroyed");
|
|
}
|
|
ArrayList<KerberosKey> keys = new ArrayList<>();
|
|
if (kp != null && !princ.equals(kp)) { // named principal
|
|
return new KerberosKey[0];
|
|
}
|
|
for (KerberosKey k: kk) {
|
|
if (k.getPrincipal().equals(princ)) {
|
|
keys.add(k);
|
|
}
|
|
}
|
|
for (KeyTab ktab: ktabs) {
|
|
if (ktab.getPrincipal() == null && ktab.isBound()) {
|
|
// legacy bound keytab. although we don't know who
|
|
// the bound principal is, it must be in allPrincs
|
|
if (!allPrincs.contains(princ)) {
|
|
continue; // skip this legacy bound keytab
|
|
}
|
|
}
|
|
for (KerberosKey k: ktab.getKeys(princ)) {
|
|
keys.add(k);
|
|
}
|
|
}
|
|
return keys.toArray(new KerberosKey[keys.size()]);
|
|
}
|
|
|
|
/**
|
|
* Gets EKeys for a principal.
|
|
* @param princ the target name initiator requests. Not null.
|
|
* @return keys for the princ, never null, might be empty
|
|
*/
|
|
public EncryptionKey[] getEKeys(PrincipalName princ) {
|
|
if (destroyed) {
|
|
throw new IllegalStateException("This object is destroyed");
|
|
}
|
|
KerberosKey[] kkeys = getKKeys(new KerberosPrincipal(princ.getName()));
|
|
if (kkeys.length == 0) {
|
|
// Fallback: old JDK does not perform real name checking. If the
|
|
// acceptor has host.sun.com but initiator requests for host,
|
|
// as long as their keys match (i.e. keys for one can decrypt
|
|
// the other's service ticket), the authentication is OK.
|
|
// There are real customers depending on this to use different
|
|
// names for a single service.
|
|
kkeys = getKKeys();
|
|
}
|
|
EncryptionKey[] ekeys = new EncryptionKey[kkeys.length];
|
|
for (int i=0; i<ekeys.length; i++) {
|
|
ekeys[i] = new EncryptionKey(
|
|
kkeys[i].getEncoded(), kkeys[i].getKeyType(),
|
|
new Integer(kkeys[i].getVersionNumber()));
|
|
}
|
|
return ekeys;
|
|
}
|
|
|
|
public Credentials getInitCred() {
|
|
if (destroyed) {
|
|
throw new IllegalStateException("This object is destroyed");
|
|
}
|
|
if (tgt == null) {
|
|
return null;
|
|
}
|
|
try {
|
|
return Krb5Util.ticketToCreds(tgt);
|
|
} catch (KrbException | IOException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void destroy() {
|
|
// Do not wipe out real keys because they are references to the
|
|
// priv creds in subject. Just make it useless.
|
|
destroyed = true;
|
|
kp = null;
|
|
ktabs.clear();
|
|
kk.clear();
|
|
tgt = null;
|
|
}
|
|
}
|