Krb5Util.java 14.7 KB
Newer Older
D
duke 已提交
1
/*
W
weijun 已提交
2
 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
D
duke 已提交
3 4 5 6
 * 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
7
 * published by the Free Software Foundation.  Oracle designates this
D
duke 已提交
8
 * particular file as subject to the "Classpath" exception as provided
9
 * by Oracle in the LICENSE file that accompanied this code.
D
duke 已提交
10 11 12 13 14 15 16 17 18 19 20
 *
 * 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.
 *
21 22 23
 * 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.
D
duke 已提交
24 25 26 27 28 29 30
 */

package sun.security.jgss.krb5;

import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.kerberos.KerberosKey;
import javax.security.auth.kerberos.KerberosPrincipal;
W
weijun 已提交
31
import javax.security.auth.kerberos.KeyTab;
D
duke 已提交
32 33 34 35
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import java.security.AccessControlContext;
import sun.security.jgss.GSSUtil;
36
import sun.security.jgss.GSSCaller;
D
duke 已提交
37 38 39 40 41

import sun.security.krb5.Credentials;
import sun.security.krb5.EncryptionKey;
import sun.security.krb5.KrbException;
import java.io.IOException;
W
weijun 已提交
42 43
import java.util.ArrayList;
import java.util.Iterator;
D
duke 已提交
44
import java.util.List;
W
weijun 已提交
45 46 47 48
import java.util.Objects;
import java.util.Set;
import sun.misc.SharedSecrets;
import sun.security.krb5.PrincipalName;
D
duke 已提交
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
/**
 * Utilities for obtaining and converting Kerberos tickets.
 *
 */
public class Krb5Util {

    static final boolean DEBUG =
        java.security.AccessController.doPrivileged(
            new sun.security.action.GetBooleanAction
            ("sun.security.krb5.debug")).booleanValue();

    /**
     * Default constructor
     */
    private Krb5Util() {  // Cannot create one of these
    }

    /**
     * Retrieve the service ticket for serverPrincipal from caller's Subject
     * or from Subject obtained by logging in, or if not found, via the
     * Ticket Granting Service using the TGT obtained from the Subject.
     *
     * Caller must have permission to:
     *    - access and update Subject's private credentials
     *    - create LoginContext
     *    - read the auth.login.defaultCallbackHandler security property
     *
     * NOTE: This method is used by JSSE Kerberos Cipher Suites
     */
78
    public static KerberosTicket getTicketFromSubjectAndTgs(GSSCaller caller,
D
duke 已提交
79 80 81 82 83 84
        String clientPrincipal, String serverPrincipal, String tgsPrincipal,
        AccessControlContext acc)
        throws LoginException, KrbException, IOException {

        // 1. Try to find service ticket in acc subject
        Subject accSubj = Subject.getSubject(acc);
W
weijun 已提交
85
        KerberosTicket ticket = SubjectComber.find(accSubj,
D
duke 已提交
86 87 88 89 90 91 92 93 94 95 96
            serverPrincipal, clientPrincipal, KerberosTicket.class);

        if (ticket != null) {
            return ticket;  // found it
        }

        Subject loginSubj = null;
        if (!GSSUtil.useSubjectCredsOnly(caller)) {
            // 2. Try to get ticket from login
            try {
                loginSubj = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
W
weijun 已提交
97
                ticket = SubjectComber.find(loginSubj,
D
duke 已提交
98 99 100 101 102 103 104 105 106 107 108 109 110 111
                    serverPrincipal, clientPrincipal, KerberosTicket.class);
                if (ticket != null) {
                    return ticket; // found it
                }
            } catch (LoginException e) {
                // No login entry to use
                // ignore and continue
            }
        }

        // Service ticket not found in subject or login
        // Try to get TGT to acquire service ticket

        // 3. Try to get TGT from acc subject
W
weijun 已提交
112
        KerberosTicket tgt = SubjectComber.find(accSubj,
D
duke 已提交
113 114 115 116 117
            tgsPrincipal, clientPrincipal, KerberosTicket.class);

        boolean fromAcc;
        if (tgt == null && loginSubj != null) {
            // 4. Try to get TGT from login subject
W
weijun 已提交
118
            tgt = SubjectComber.find(loginSubj,
D
duke 已提交
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
                tgsPrincipal, clientPrincipal, KerberosTicket.class);
            fromAcc = false;
        } else {
            fromAcc = true;
        }

        // 5. Try to get service ticket using TGT
        if (tgt != null) {
            Credentials tgtCreds = ticketToCreds(tgt);
            Credentials serviceCreds = Credentials.acquireServiceCreds(
                        serverPrincipal, tgtCreds);
            if (serviceCreds != null) {
                ticket = credsToTicket(serviceCreds);

                // Store service ticket in acc's Subject
                if (fromAcc && accSubj != null && !accSubj.isReadOnly()) {
                    accSubj.getPrivateCredentials().add(ticket);
                }
            }
        }
        return ticket;
    }

    /**
     * Retrieves the ticket corresponding to the client/server principal
     * pair from the Subject in the specified AccessControlContext.
     * If the ticket can not be found in the Subject, and if
     * useSubjectCredsOnly is false, then obtain ticket from
     * a LoginContext.
     */
149
    static KerberosTicket getTicket(GSSCaller caller,
D
duke 已提交
150 151 152 153 154
        String clientPrincipal, String serverPrincipal,
        AccessControlContext acc) throws LoginException {

        // Try to get ticket from acc's Subject
        Subject accSubj = Subject.getSubject(acc);
W
weijun 已提交
155
        KerberosTicket ticket =
D
duke 已提交
156 157 158 159 160 161
            SubjectComber.find(accSubj, serverPrincipal, clientPrincipal,
                  KerberosTicket.class);

        // Try to get ticket from Subject obtained from GSSUtil
        if (ticket == null && !GSSUtil.useSubjectCredsOnly(caller)) {
            Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
W
weijun 已提交
162
            ticket = SubjectComber.find(subject,
D
duke 已提交
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
                serverPrincipal, clientPrincipal, KerberosTicket.class);
        }
        return ticket;
    }

    /**
     * Retrieves the caller's Subject, or Subject obtained by logging in
     * via the specified caller.
     *
     * Caller must have permission to:
     *    - access the Subject
     *    - create LoginContext
     *    - read the auth.login.defaultCallbackHandler security property
     *
     * NOTE: This method is used by JSSE Kerberos Cipher Suites
     */
179
    public static Subject getSubject(GSSCaller caller,
D
duke 已提交
180 181 182 183 184 185 186 187 188 189 190 191
        AccessControlContext acc) throws LoginException {

        // Try to get the Subject from acc
        Subject subject = Subject.getSubject(acc);

        // Try to get Subject obtained from GSSUtil
        if (subject == null && !GSSUtil.useSubjectCredsOnly(caller)) {
            subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
        }
        return subject;
    }

W
weijun 已提交
192 193 194 195 196 197 198 199 200 201
    // A special KerberosKey, used as keys read from a KeyTab object.
    // Each time new keys are read from KeyTab objects in the private
    // credentials set, old ones are removed and new ones added.
    public static class KeysFromKeyTab extends KerberosKey {
        public KeysFromKeyTab(KerberosKey key) {
            super(key.getPrincipal(), key.getEncoded(),
                    key.getKeyType(), key.getVersionNumber());
        }
    }

D
duke 已提交
202
    /**
W
weijun 已提交
203 204 205 206 207
     * Credentials of a service, the private secret to authenticate its
     * identity, which can be:
     *   1. Some KerberosKeys (generated from password)
     *   2. A KeyTab (for a typical service)
     *   3. A TGT (for a user2user service. Not supported yet)
D
duke 已提交
208
     *
W
weijun 已提交
209 210 211 212 213
     * Note that some creds can coexist. For example, a user2user service
     * can use its keytab (or keys) if the client can successfully obtain a
     * normal service ticket, otherwise, it can uses the TGT (actually, the
     * session key of the TGT) if the client can only acquire a service ticket
     * of ENC-TKT-IN-SKEY style.
D
duke 已提交
214
     */
W
weijun 已提交
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
    public static class ServiceCreds {
        private KerberosPrincipal kp;
        private List<KeyTab> ktabs;
        private List<KerberosKey> kk;
        private Subject subj;
        //private KerberosTicket tgt;   // user2user, not supported yet

        private static ServiceCreds getInstance(
                Subject subj, String serverPrincipal) {

            ServiceCreds sc = new ServiceCreds();
            sc.subj = subj;

            for (KerberosPrincipal p: subj.getPrincipals(KerberosPrincipal.class)) {
                if (serverPrincipal == null ||
                        p.getName().equals(serverPrincipal)) {
                    sc.kp = p;
                    serverPrincipal = p.getName();
                    break;
                }
            }
            if (sc.kp == null) {
                // Compatibility with old behavior: even when there is no
                // KerberosPrincipal, we can find one from KerberosKeys
                List<KerberosKey> keys = SubjectComber.findMany(
                        subj, null, null, KerberosKey.class);
                if (!keys.isEmpty()) {
                    sc.kp = keys.get(0).getPrincipal();
                    serverPrincipal = sc.kp.getName();
                    if (DEBUG) {
                        System.out.println(">>> ServiceCreds: no kp?"
                                + " find one from kk: " + serverPrincipal);
                    }
                } else {
                    return null;
                }
            }
            sc.ktabs = SubjectComber.findMany(
                        subj, null, null, KeyTab.class);
            sc.kk = SubjectComber.findMany(
                        subj, serverPrincipal, null, KerberosKey.class);
            if (sc.ktabs.isEmpty() && sc.kk.isEmpty()) {
                return null;
            }
            return sc;
        }

        public String getName() {
            return kp.getName();
        }

        public KerberosKey[] getKKeys() {
            if (ktabs.isEmpty()) {
                return kk.toArray(new KerberosKey[kk.size()]);
            } else {
                List<KerberosKey> keys = new ArrayList<>();
                for (KeyTab ktab: ktabs) {
                    for (KerberosKey k: ktab.getKeys(kp)) {
                        keys.add(k);
                    }
                }
                // Compatibility: also add keys to privCredSet. Remove old
                // ones first, only remove those from keytab.
                if (!subj.isReadOnly()) {
                    Set<Object> pcs = subj.getPrivateCredentials();
                    synchronized (pcs) {
                        Iterator<Object> iterator = pcs.iterator();
                        while (iterator.hasNext()) {
                            Object obj = iterator.next();
                            if (obj instanceof KeysFromKeyTab) {
                                KerberosKey key = (KerberosKey)obj;
                                if (Objects.equals(key.getPrincipal(), kp)) {
                                    iterator.remove();
                                }
                            }
                        }
                    }
                    for (KerberosKey key: keys) {
                        subj.getPrivateCredentials().add(new KeysFromKeyTab(key));
                    }
                }
                return keys.toArray(new KerberosKey[keys.size()]);
            }
        }

        public EncryptionKey[] getEKeys() {
            KerberosKey[] 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 void destroy() {
            kp = null;
            ktabs = null;
            kk = null;
        }
    }
    /**
     * Retrieves the ServiceCreds for the specified server principal from
     * the Subject in the specified AccessControlContext. If not found, and if
     * useSubjectCredsOnly is false, then obtain from a LoginContext.
     *
     * NOTE: This method is also used by JSSE Kerberos Cipher Suites
     */
    public static ServiceCreds getServiceCreds(GSSCaller caller,
D
duke 已提交
325 326 327 328
        String serverPrincipal, AccessControlContext acc)
                throws LoginException {

        Subject accSubj = Subject.getSubject(acc);
W
weijun 已提交
329 330 331
        ServiceCreds sc = null;
        if (accSubj != null) {
            sc = ServiceCreds.getInstance(accSubj, serverPrincipal);
D
duke 已提交
332
        }
W
weijun 已提交
333 334 335
        if (sc == null && !GSSUtil.useSubjectCredsOnly(caller)) {
            Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
            sc = ServiceCreds.getInstance(subject, serverPrincipal);
D
duke 已提交
336
        }
W
weijun 已提交
337
        return sc;
D
duke 已提交
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
    }

    public static KerberosTicket credsToTicket(Credentials serviceCreds) {
        EncryptionKey sessionKey =  serviceCreds.getSessionKey();
        return new KerberosTicket(
            serviceCreds.getEncoded(),
            new KerberosPrincipal(serviceCreds.getClient().getName()),
            new KerberosPrincipal(serviceCreds.getServer().getName(),
                                KerberosPrincipal.KRB_NT_SRV_INST),
            sessionKey.getBytes(),
            sessionKey.getEType(),
            serviceCreds.getFlags(),
            serviceCreds.getAuthTime(),
            serviceCreds.getStartTime(),
            serviceCreds.getEndTime(),
            serviceCreds.getRenewTill(),
            serviceCreds.getClientAddresses());
    };

    public static Credentials ticketToCreds(KerberosTicket kerbTicket)
        throws KrbException, IOException {
        return new Credentials(
            kerbTicket.getEncoded(),
            kerbTicket.getClient().getName(),
            kerbTicket.getServer().getName(),
            kerbTicket.getSessionKey().getEncoded(),
            kerbTicket.getSessionKeyType(),
            kerbTicket.getFlags(),
            kerbTicket.getAuthTime(),
            kerbTicket.getStartTime(),
            kerbTicket.getEndTime(),
            kerbTicket.getRenewTill(),
            kerbTicket.getClientAddresses());
    }
W
weijun 已提交
372 373 374 375 376 377 378 379 380 381 382 383 384

    /**
     * A helper method to get EncryptionKeys from a javax..KeyTab
     * @param ktab the javax..KeyTab class
     * @param cname the PrincipalName
     * @return the EKeys, never null, might be empty
     */
    public static EncryptionKey[] keysFromJavaxKeyTab(
            KeyTab ktab, PrincipalName cname) {
        return SharedSecrets.getJavaxSecurityAuthKerberosAccess().
                keyTabGetEncryptionKeys(ktab, cname);
    }

D
duke 已提交
385
}