blob: 5fa0c3f36461dec9a85a1a31fee950645d989f09 [file] [log] [blame]
/*
* 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(),
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;
}
}