CredentialsUtil.java 16.5 KB
Newer Older
D
duke 已提交
1
/*
2
 * Copyright (c) 2001, 2019, 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 31 32 33 34 35
 */

/*
 *
 *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
 *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
 */

package sun.security.krb5.internal;

import sun.security.krb5.*;
import java.io.IOException;
36 37
import java.util.LinkedList;
import java.util.List;
D
duke 已提交
38 39 40 41 42 43 44 45 46 47

/**
 * This class is a utility that contains much of the TGS-Exchange
 * protocol. It is used by ../Credentials.java for service ticket
 * acquisition in both the normal and the x-realm case.
 */
public class CredentialsUtil {

    private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    /**
     * Used by a middle server to acquire credentials on behalf of a
     * client to itself using the S4U2self extension.
     * @param client the client to impersonate
     * @param ccreds the TGT of the middle service
     * @return the new creds (cname=client, sname=middle)
     */
    public static Credentials acquireS4U2selfCreds(PrincipalName client,
            Credentials ccreds) throws KrbException, IOException {
        String uRealm = client.getRealmString();
        String localRealm = ccreds.getClient().getRealmString();
        if (!uRealm.equals(localRealm)) {
            // TODO: we do not support kerberos referral now
            throw new KrbException("Cross realm impersonation not supported");
        }
63 64 65
        if (!ccreds.isForwardable()) {
            throw new KrbException("S4U2self needs a FORWARDABLE ticket");
        }
66 67 68 69 70
        Credentials creds = serviceCreds(KDCOptions.with(KDCOptions.FORWARDABLE),
                ccreds, ccreds.getClient(), ccreds.getClient(), null,
                new PAData[] {new PAData(Krb5.PA_FOR_USER,
                        new PAForUserEnc(client,
                            ccreds.getSessionKey()).asn1Encode())});
71 72 73
        if (!creds.getClient().equals(client)) {
            throw new KrbException("S4U2self request not honored by KDC");
        }
74 75 76
        if (!creds.isForwardable()) {
            throw new KrbException("S4U2self ticket must be FORWARDABLE");
        }
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
        return creds;
    }

    /**
     * Used by a middle server to acquire a service ticket to a backend
     * server using the S4U2proxy extension.
     * @param backend the name of the backend service
     * @param second the client's service ticket to the middle server
     * @param ccreds the TGT of the middle server
     * @return the creds (cname=client, sname=backend)
     */
    public static Credentials acquireS4U2proxyCreds(
                String backend, Ticket second,
                PrincipalName client, Credentials ccreds)
            throws KrbException, IOException {
92 93 94 95
        Credentials creds = serviceCreds(KDCOptions.with(
                KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE),
                ccreds, ccreds.getClient(), new PrincipalName(backend),
                new Ticket[] {second}, null);
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
        if (!creds.getClient().equals(client)) {
            throw new KrbException("S4U2proxy request not honored by KDC");
        }
        return creds;
    }

    /**
     * Acquires credentials for a specified service using initial
     * credential. When the service has a different realm from the initial
     * credential, we do cross-realm authentication - first, we use the
     * current credential to get a cross-realm credential from the local KDC,
     * then use that cross-realm credential to request service credential
     * from the foreign KDC.
     *
     * @param service the name of service principal
     * @param ccreds client's initial credential
     */
D
duke 已提交
113 114
    public static Credentials acquireServiceCreds(
                String service, Credentials ccreds)
115
            throws KrbException, IOException {
116 117 118
        PrincipalName sname = new PrincipalName(service,
                PrincipalName.KRB_NT_SRV_HST);
        return serviceCreds(sname, ccreds);
119 120 121 122 123
    }

    /**
     * Gets a TGT to another realm
     * @param localRealm this realm
124
     * @param serviceRealm the other realm, cannot equals to localRealm
125 126 127 128 129 130 131 132 133
     * @param ccreds TGT in this realm
     * @param okAsDelegate an [out] argument to receive the okAsDelegate
     * property. True only if all realms allow delegation.
     * @return the TGT for the other realm, null if cannot find a path
     * @throws KrbException if something goes wrong
     */
    private static Credentials getTGTforRealm(String localRealm,
            String serviceRealm, Credentials ccreds, boolean[] okAsDelegate)
            throws KrbException {
D
duke 已提交
134 135 136 137 138 139

        // Get a list of realms to traverse
        String[] realms = Realm.getRealmsList(localRealm, serviceRealm);

        int i = 0, k = 0;
        Credentials cTgt = null, newTgt = null, theTgt = null;
140
        PrincipalName tempService = null;
141
        String newTgtRealm = null;
D
duke 已提交
142

143 144
        okAsDelegate[0] = true;
        for (cTgt = ccreds, i = 0; i < realms.length;) {
145
            tempService = PrincipalName.tgsService(serviceRealm, realms[i]);
D
duke 已提交
146

147 148 149 150
            if (DEBUG) {
                System.out.println(
                        ">>> Credentials acquireServiceCreds: main loop: ["
                        + i +"] tempService=" + tempService);
D
duke 已提交
151 152 153 154 155 156 157 158
            }

            try {
                newTgt = serviceCreds(tempService, cTgt);
            } catch (Exception exc) {
                newTgt = null;
            }

159 160 161
            if (newTgt == null) {
                if (DEBUG) {
                    System.out.println(">>> Credentials acquireServiceCreds: "
162
                            + "no tgt; searching thru capath");
D
duke 已提交
163 164 165
                }

                /*
166
                 * No tgt found. Let's go thru the realms list one by one.
D
duke 已提交
167
                 */
168 169
                for (newTgt = null, k = i+1;
                        newTgt == null && k < realms.length; k++) {
170
                    tempService = PrincipalName.tgsService(realms[k], realms[i]);
171 172 173 174 175
                    if (DEBUG) {
                        System.out.println(
                                ">>> Credentials acquireServiceCreds: "
                                + "inner loop: [" + k
                                + "] tempService=" + tempService);
D
duke 已提交
176 177 178 179 180 181 182 183 184
                    }
                    try {
                        newTgt = serviceCreds(tempService, cTgt);
                    } catch (Exception exc) {
                        newTgt = null;
                    }
                }
            } // Ends 'if (newTgt == null)'

185 186 187 188
            if (newTgt == null) {
                if (DEBUG) {
                    System.out.println(">>> Credentials acquireServiceCreds: "
                            + "no tgt; cannot get creds");
D
duke 已提交
189 190 191 192 193 194 195 196 197
                }
                break;
            }

            /*
             * We have a tgt. It may or may not be for the target.
             * If it's for the target realm, we're done looking for a tgt.
             */
            newTgtRealm = newTgt.getServer().getInstanceComponent();
198 199
            if (okAsDelegate[0] && !newTgt.checkDelegate()) {
                if (DEBUG) {
W
weijun 已提交
200 201 202 203
                    System.out.println(">>> Credentials acquireServiceCreds: " +
                            "global OK-AS-DELEGATE turned off at " +
                            newTgt.getServer());
                }
204
                okAsDelegate[0] = false;
W
weijun 已提交
205
            }
D
duke 已提交
206

207 208 209
            if (DEBUG) {
                System.out.println(">>> Credentials acquireServiceCreds: "
                        + "got tgt");
D
duke 已提交
210 211
            }

212
            if (newTgtRealm.equals(serviceRealm)) {
D
duke 已提交
213 214 215 216 217 218 219 220 221 222
                /* We got the right tgt */
                theTgt = newTgt;
                break;
            }

            /*
             * The new tgt is not for the target realm.
             * See if the realm of the new tgt is in the list of realms
             * and continue looking from there.
             */
223 224
            for (k = i+1; k < realms.length; k++) {
                if (newTgtRealm.equals(realms[k])) {
D
duke 已提交
225 226 227 228
                    break;
                }
            }

229
            if (k < realms.length) {
D
duke 已提交
230 231 232 233 234 235 236
                /*
                 * (re)set the counter so we start looking
                 * from the realm we just obtained a tgt for.
                 */
                i = k;
                cTgt = newTgt;

237 238 239
                if (DEBUG) {
                    System.out.println(">>> Credentials acquireServiceCreds: "
                            + "continuing with main loop counter reset to " + i);
D
duke 已提交
240 241 242
                }
                continue;
            }
243
            else {
D
duke 已提交
244
                /*
245
                 * The new tgt's realm is not in the hierarchy of realms.
D
duke 已提交
246 247 248 249 250 251 252 253
                 * It's probably not safe to get a tgt from
                 * a tgs that is outside the known list of realms.
                 * Give up now.
                 */
                break;
            }
        } // Ends outermost/main 'for' loop

254
        return theTgt;
D
duke 已提交
255 256 257 258 259 260
    }

   /*
    * This method does the real job to request the service credential.
    */
    private static Credentials serviceCreds(
261
            PrincipalName service, Credentials ccreds)
D
duke 已提交
262
            throws KrbException, IOException {
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
        return serviceCreds(new KDCOptions(), ccreds,
                ccreds.getClient(), service, null, null);
    }

    /*
     * Obtains credentials for a service (TGS).
     * Cross-realm referrals are handled if enabled. A fallback scheme
     * without cross-realm referrals supports is used in case of server
     * error to maintain backward compatibility.
     */
    private static Credentials serviceCreds(
            KDCOptions options, Credentials asCreds,
            PrincipalName cname, PrincipalName sname,
            Ticket[] additionalTickets, PAData[] extraPAs)
            throws KrbException, IOException {
        if (!Config.DISABLE_REFERRALS) {
            try {
                return serviceCredsReferrals(options, asCreds,
                        cname, sname, additionalTickets, extraPAs);
            } catch (KrbException e) {
                // Server may raise an error if CANONICALIZE is true.
                // Try CANONICALIZE false.
            }
        }
287 288 289
        return serviceCredsSingle(options, asCreds, cname,
                asCreds.getClientAlias(), sname, sname, additionalTickets,
                extraPAs);
290 291 292 293 294 295 296 297 298 299 300 301 302 303
    }

    /*
     * Obtains credentials for a service (TGS).
     * May handle and follow cross-realm referrals as defined by RFC 6806.
     */
    private static Credentials serviceCredsReferrals(
            KDCOptions options, Credentials asCreds,
            PrincipalName cname, PrincipalName sname,
            Ticket[] additionalTickets, PAData[] extraPAs)
            throws KrbException, IOException {
        options = new KDCOptions(options.toBooleanArray());
        options.set(KDCOptions.CANONICALIZE, true);
        PrincipalName cSname = sname;
304
        PrincipalName refSname = sname; // May change with referrals
305 306 307
        Credentials creds = null;
        boolean isReferral = false;
        List<String> referrals = new LinkedList<>();
308
        PrincipalName clientAlias = asCreds.getClientAlias();
309 310
        while (referrals.size() <= Config.MAX_REFERRALS) {
            ReferralsCache.ReferralCacheEntry ref =
311
                    ReferralsCache.get(cname, sname, refSname.getRealmString());
312 313
            String toRealm = null;
            if (ref == null) {
314 315 316
                creds = serviceCredsSingle(options, asCreds, cname,
                        clientAlias, refSname, cSname, additionalTickets,
                        extraPAs);
317
                PrincipalName server = creds.getServer();
318
                if (!refSname.equals(server)) {
319 320 321 322
                    String[] serverNameStrings = server.getNameStrings();
                    if (serverNameStrings.length == 2 &&
                        serverNameStrings[0].equals(
                                PrincipalName.TGS_DEFAULT_SRV_NAME) &&
323
                        !refSname.getRealmAsString().equals(serverNameStrings[1])) {
324 325
                        // Server Name (sname) has the following format:
                        //      krbtgt/TO-REALM.COM@FROM-REALM.COM
326
                        ReferralsCache.put(cname, sname, server.getRealmString(),
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
                                serverNameStrings[1], creds);
                        toRealm = serverNameStrings[1];
                        isReferral = true;
                        asCreds = creds;
                    }
                }
            } else {
                toRealm = ref.getToRealm();
                asCreds = ref.getCreds();
                isReferral = true;
            }
            if (isReferral) {
                if (referrals.contains(toRealm)) {
                    // Referrals loop detected
                    return null;
                }
343 344
                refSname = new PrincipalName(refSname.getNameString(),
                        refSname.getNameType(), toRealm);
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
                referrals.add(toRealm);
                isReferral = false;
                continue;
            }
            break;
        }
        return creds;
    }

    /*
     * Obtains credentials for a service (TGS).
     * If the service realm is different than the one in the TGT, a new TGT for
     * the service realm is obtained first (see getTGTforRealm call). This is
     * not expected when following cross-realm referrals because the referral
     * TGT realm matches the service realm.
     */
    private static Credentials serviceCredsSingle(
            KDCOptions options, Credentials asCreds,
363 364
            PrincipalName cname, PrincipalName clientAlias,
            PrincipalName refSname, PrincipalName sname,
365 366 367 368 369 370
            Ticket[] additionalTickets, PAData[] extraPAs)
            throws KrbException, IOException {
        Credentials theCreds = null;
        boolean[] okAsDelegate = new boolean[]{true};
        String[] serverAsCredsNames = asCreds.getServer().getNameStrings();
        String tgtRealm = serverAsCredsNames[1];
371
        String serviceRealm = refSname.getRealmString();
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
        if (!serviceRealm.equals(tgtRealm)) {
            // This is a cross-realm service request
            if (DEBUG) {
                System.out.println(">>> serviceCredsSingle:" +
                        " cross-realm authentication");
                System.out.println(">>> serviceCredsSingle:" +
                        " obtaining credentials from " + tgtRealm +
                        " to " + serviceRealm);
            }
            Credentials newTgt = getTGTforRealm(tgtRealm, serviceRealm,
                    asCreds, okAsDelegate);
            if (newTgt == null) {
                throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED,
                        "No service creds");
            }
            if (DEBUG) {
                System.out.println(">>> Cross-realm TGT Credentials" +
                        " serviceCredsSingle: ");
                Credentials.printDebug(newTgt);
            }
            asCreds = newTgt;
            cname = asCreds.getClient();
        } else if (DEBUG) {
            System.out.println(">>> Credentials serviceCredsSingle:" +
                    " same realm");
        }
398 399
        KrbTgsReq req = new KrbTgsReq(options, asCreds, cname, clientAlias,
                refSname, sname, additionalTickets, extraPAs);
400 401 402 403 404 405 406 407 408 409 410
        theCreds = req.sendAndGetCreds();
        if (theCreds != null) {
            if (DEBUG) {
                System.out.println(">>> TGS credentials serviceCredsSingle:");
                Credentials.printDebug(theCreds);
            }
            if (!okAsDelegate[0]) {
                theCreds.resetDelegate();
            }
        }
        return theCreds;
D
duke 已提交
411 412
    }
}