1/*
2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 *
28 *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
29 *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
30 */
31
32package sun.security.krb5;
33
34import sun.security.krb5.internal.*;
35import sun.security.krb5.internal.crypto.*;
36import java.io.IOException;
37import java.net.UnknownHostException;
38
39/**
40 * This class encapsulates a Kerberos TGS-REQ that is sent from the
41 * client to the KDC.
42 */
43public class KrbTgsReq {
44
45    private PrincipalName princName;
46    private PrincipalName servName;
47    private TGSReq tgsReqMessg;
48    private KerberosTime ctime;
49    private Ticket secondTicket = null;
50    private boolean useSubkey = false;
51    EncryptionKey tgsReqKey;
52
53    private static final boolean DEBUG = Krb5.DEBUG;
54
55    private byte[] obuf;
56    private byte[] ibuf;
57
58    // Used in CredentialsUtil
59    public KrbTgsReq(Credentials asCreds,
60                     PrincipalName sname)
61        throws KrbException, IOException {
62        this(new KDCOptions(),
63            asCreds,
64            sname,
65            null, // KerberosTime from
66            null, // KerberosTime till
67            null, // KerberosTime rtime
68            null, // eTypes, // null, // int[] eTypes
69            null, // HostAddresses addresses
70            null, // AuthorizationData authorizationData
71            null, // Ticket[] additionalTickets
72            null); // EncryptionKey subSessionKey
73    }
74
75    // S4U2proxy
76    public KrbTgsReq(Credentials asCreds,
77                     Ticket second,
78                     PrincipalName sname)
79            throws KrbException, IOException {
80        this(KDCOptions.with(KDCOptions.CNAME_IN_ADDL_TKT,
81                KDCOptions.FORWARDABLE),
82            asCreds,
83            sname,
84            null,
85            null,
86            null,
87            null,
88            null,
89            null,
90            new Ticket[] {second}, // the service ticket
91            null);
92    }
93
94    // S4U2user
95    public KrbTgsReq(Credentials asCreds,
96                     PrincipalName sname,
97                     PAData extraPA)
98        throws KrbException, IOException {
99        this(KDCOptions.with(KDCOptions.FORWARDABLE),
100            asCreds,
101            asCreds.getClient(),
102            sname,
103            null,
104            null,
105            null,
106            null,
107            null,
108            null,
109            null,
110            null,
111            extraPA); // the PA-FOR-USER
112    }
113
114    // Called by Credentials, KrbCred
115    KrbTgsReq(
116            KDCOptions options,
117            Credentials asCreds,
118            PrincipalName sname,
119            KerberosTime from,
120            KerberosTime till,
121            KerberosTime rtime,
122            int[] eTypes,
123            HostAddresses addresses,
124            AuthorizationData authorizationData,
125            Ticket[] additionalTickets,
126            EncryptionKey subKey) throws KrbException, IOException {
127        this(options, asCreds, asCreds.getClient(), sname,
128                from, till, rtime, eTypes, addresses,
129                authorizationData, additionalTickets, subKey, null);
130    }
131
132    private KrbTgsReq(
133            KDCOptions options,
134            Credentials asCreds,
135            PrincipalName cname,
136            PrincipalName sname,
137            KerberosTime from,
138            KerberosTime till,
139            KerberosTime rtime,
140            int[] eTypes,
141            HostAddresses addresses,
142            AuthorizationData authorizationData,
143            Ticket[] additionalTickets,
144            EncryptionKey subKey,
145            PAData extraPA) throws KrbException, IOException {
146
147        princName = cname;
148        servName = sname;
149        ctime = KerberosTime.now();
150
151        // check if they are valid arguments. The optional fields
152        // should be consistent with settings in KDCOptions.
153
154        if (options.get(KDCOptions.FORWARDABLE) &&
155                (!(asCreds.flags.get(Krb5.TKT_OPTS_FORWARDABLE)))) {
156            options.set(KDCOptions.FORWARDABLE, false);
157        }
158        if (options.get(KDCOptions.FORWARDED)) {
159            if (!(asCreds.flags.get(KDCOptions.FORWARDABLE)))
160                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
161        }
162        if (options.get(KDCOptions.PROXIABLE) &&
163                (!(asCreds.flags.get(Krb5.TKT_OPTS_PROXIABLE)))) {
164            throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
165        }
166        if (options.get(KDCOptions.PROXY)) {
167            if (!(asCreds.flags.get(KDCOptions.PROXIABLE)))
168                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
169        }
170        if (options.get(KDCOptions.ALLOW_POSTDATE) &&
171                (!(asCreds.flags.get(Krb5.TKT_OPTS_MAY_POSTDATE)))) {
172            throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
173        }
174        if (options.get(KDCOptions.RENEWABLE) &&
175                (!(asCreds.flags.get(Krb5.TKT_OPTS_RENEWABLE)))) {
176            throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
177        }
178
179        if (options.get(KDCOptions.POSTDATED)) {
180            if (!(asCreds.flags.get(KDCOptions.POSTDATED)))
181                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
182        } else {
183            if (from != null)  from = null;
184        }
185        if (options.get(KDCOptions.RENEWABLE)) {
186            if (!(asCreds.flags.get(KDCOptions.RENEWABLE)))
187                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
188        } else {
189            if (rtime != null)  rtime = null;
190        }
191        if (options.get(KDCOptions.ENC_TKT_IN_SKEY) || options.get(KDCOptions.CNAME_IN_ADDL_TKT)) {
192            if (additionalTickets == null)
193                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
194            // in TGS_REQ there could be more than one additional
195            // tickets,  but in file-based credential cache,
196            // there is only one additional ticket field.
197            secondTicket = additionalTickets[0];
198        } else {
199            if (additionalTickets != null)
200                additionalTickets = null;
201        }
202
203        tgsReqMessg = createRequest(
204                options,
205                asCreds.ticket,
206                asCreds.key,
207                ctime,
208                princName,
209                servName,
210                from,
211                till,
212                rtime,
213                eTypes,
214                addresses,
215                authorizationData,
216                additionalTickets,
217                subKey,
218                extraPA);
219        obuf = tgsReqMessg.asn1Encode();
220
221        // XXX We need to revisit this to see if can't move it
222        // up such that FORWARDED flag set in the options
223        // is included in the marshaled request.
224        /*
225         * If this is based on a forwarded ticket, record that in the
226         * options, because the returned TgsRep will contain the
227         * FORWARDED flag set.
228         */
229        if (asCreds.flags.get(KDCOptions.FORWARDED))
230            options.set(KDCOptions.FORWARDED, true);
231
232
233    }
234
235    /**
236     * Sends a TGS request to the realm of the target.
237     * @throws KrbException
238     * @throws IOException
239     */
240    public void send() throws IOException, KrbException {
241        String realmStr = null;
242        if (servName != null)
243            realmStr = servName.getRealmString();
244        KdcComm comm = new KdcComm(realmStr);
245        ibuf = comm.send(obuf);
246    }
247
248    public KrbTgsRep getReply()
249        throws KrbException, IOException {
250        return new KrbTgsRep(ibuf, this);
251    }
252
253    /**
254     * Sends the request, waits for a reply, and returns the Credentials.
255     * Used in Credentials, KrbCred, and internal/CredentialsUtil.
256     */
257    public Credentials sendAndGetCreds() throws IOException, KrbException {
258        KrbTgsRep tgs_rep = null;
259        String kdc = null;
260        send();
261        tgs_rep = getReply();
262        return tgs_rep.getCreds();
263    }
264
265    KerberosTime getCtime() {
266        return ctime;
267    }
268
269    private TGSReq createRequest(
270                         KDCOptions kdc_options,
271                         Ticket ticket,
272                         EncryptionKey key,
273                         KerberosTime ctime,
274                         PrincipalName cname,
275                         PrincipalName sname,
276                         KerberosTime from,
277                         KerberosTime till,
278                         KerberosTime rtime,
279                         int[] eTypes,
280                         HostAddresses addresses,
281                         AuthorizationData authorizationData,
282                         Ticket[] additionalTickets,
283                         EncryptionKey subKey,
284                         PAData extraPA)
285        throws IOException, KrbException, UnknownHostException {
286        KerberosTime req_till = null;
287        if (till == null) {
288            req_till = new KerberosTime(0);
289        } else {
290            req_till = till;
291        }
292
293        /*
294         * RFC 4120, Section 5.4.2.
295         * For KRB_TGS_REP, the ciphertext is encrypted in the
296         * sub-session key from the Authenticator, or if absent,
297         * the session key from the ticket-granting ticket used
298         * in the request.
299         *
300         * To support this, use tgsReqKey to remember which key to use.
301         */
302        tgsReqKey = key;
303
304        int[] req_eTypes = null;
305        if (eTypes == null) {
306            req_eTypes = EType.getDefaults("default_tgs_enctypes");
307        } else {
308            req_eTypes = eTypes;
309        }
310
311        EncryptionKey reqKey = null;
312        EncryptedData encAuthorizationData = null;
313        if (authorizationData != null) {
314            byte[] ad = authorizationData.asn1Encode();
315            if (subKey != null) {
316                reqKey = subKey;
317                tgsReqKey = subKey;    // Key to use to decrypt reply
318                useSubkey = true;
319                encAuthorizationData = new EncryptedData(reqKey, ad,
320                    KeyUsage.KU_TGS_REQ_AUTH_DATA_SUBKEY);
321            } else
322                encAuthorizationData = new EncryptedData(key, ad,
323                    KeyUsage.KU_TGS_REQ_AUTH_DATA_SESSKEY);
324        }
325
326        KDCReqBody reqBody = new KDCReqBody(
327                                            kdc_options,
328                                            cname,
329                                            sname,
330                                            from,
331                                            req_till,
332                                            rtime,
333                                            Nonce.value(),
334                                            req_eTypes,
335                                            addresses,
336                                            encAuthorizationData,
337                                            additionalTickets);
338
339        byte[] temp = reqBody.asn1Encode(Krb5.KRB_TGS_REQ);
340        // if the checksum type is one of the keyed checksum types,
341        // use session key.
342        Checksum cksum;
343        switch (Checksum.CKSUMTYPE_DEFAULT) {
344        case Checksum.CKSUMTYPE_RSA_MD4_DES:
345        case Checksum.CKSUMTYPE_DES_MAC:
346        case Checksum.CKSUMTYPE_DES_MAC_K:
347        case Checksum.CKSUMTYPE_RSA_MD4_DES_K:
348        case Checksum.CKSUMTYPE_RSA_MD5_DES:
349        case Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD:
350        case Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR:
351        case Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128:
352        case Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256:
353            cksum = new Checksum(Checksum.CKSUMTYPE_DEFAULT, temp, key,
354                KeyUsage.KU_PA_TGS_REQ_CKSUM);
355            break;
356        case Checksum.CKSUMTYPE_CRC32:
357        case Checksum.CKSUMTYPE_RSA_MD4:
358        case Checksum.CKSUMTYPE_RSA_MD5:
359        default:
360            cksum = new Checksum(Checksum.CKSUMTYPE_DEFAULT, temp);
361        }
362
363        // Usage will be KeyUsage.KU_PA_TGS_REQ_AUTHENTICATOR
364
365        byte[] tgs_ap_req = new KrbApReq(
366                                         new APOptions(),
367                                         ticket,
368                                         key,
369                                         cname,
370                                         cksum,
371                                         ctime,
372                                         reqKey,
373                                         null,
374                                         null).getMessage();
375
376        PAData tgsPAData = new PAData(Krb5.PA_TGS_REQ, tgs_ap_req);
377        return new TGSReq(
378                extraPA != null ?
379                    new PAData[] {extraPA, tgsPAData } :
380                    new PAData[] {tgsPAData},
381                reqBody);
382    }
383
384    TGSReq getMessage() {
385        return tgsReqMessg;
386    }
387
388    Ticket getSecondTicket() {
389        return secondTicket;
390    }
391
392    private static void debug(String message) {
393        //      System.err.println(">>> KrbTgsReq: " + message);
394    }
395
396    boolean usedSubkey() {
397        return useSubkey;
398    }
399
400}
401