1/*
2 * Copyright (c) 2000, 2017, 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;
38import java.time.Instant;
39
40/**
41 * This class encapsulates a Kerberos TGS-REQ that is sent from the
42 * client to the KDC.
43 */
44public class KrbTgsReq {
45
46    private PrincipalName princName;
47    private PrincipalName servName;
48    private TGSReq tgsReqMessg;
49    private KerberosTime ctime;
50    private Ticket secondTicket = null;
51    private boolean useSubkey = false;
52    EncryptionKey tgsReqKey;
53
54    private static final boolean DEBUG = Krb5.DEBUG;
55
56    private byte[] obuf;
57    private byte[] ibuf;
58
59    // Used in CredentialsUtil
60    public KrbTgsReq(Credentials asCreds,
61                     PrincipalName sname)
62        throws KrbException, IOException {
63        this(new KDCOptions(),
64            asCreds,
65            sname,
66            null, // KerberosTime from
67            null, // KerberosTime till
68            null, // KerberosTime rtime
69            null, // eTypes, // null, // int[] eTypes
70            null, // HostAddresses addresses
71            null, // AuthorizationData authorizationData
72            null, // Ticket[] additionalTickets
73            null); // EncryptionKey subSessionKey
74    }
75
76    // S4U2proxy
77    public KrbTgsReq(Credentials asCreds,
78                     Ticket second,
79                     PrincipalName sname)
80            throws KrbException, IOException {
81        this(KDCOptions.with(KDCOptions.CNAME_IN_ADDL_TKT,
82                KDCOptions.FORWARDABLE),
83            asCreds,
84            sname,
85            null,
86            null,
87            null,
88            null,
89            null,
90            null,
91            new Ticket[] {second}, // the service ticket
92            null);
93    }
94
95    // S4U2user
96    public KrbTgsReq(Credentials asCreds,
97                     PrincipalName sname,
98                     PAData extraPA)
99        throws KrbException, IOException {
100        this(KDCOptions.with(KDCOptions.FORWARDABLE),
101            asCreds,
102            asCreds.getClient(),
103            sname,
104            null,
105            null,
106            null,
107            null,
108            null,
109            null,
110            null,
111            null,
112            extraPA); // the PA-FOR-USER
113    }
114
115    // Called by Credentials, KrbCred
116    KrbTgsReq(
117            KDCOptions options,
118            Credentials asCreds,
119            PrincipalName sname,
120            KerberosTime from,
121            KerberosTime till,
122            KerberosTime rtime,
123            int[] eTypes,
124            HostAddresses addresses,
125            AuthorizationData authorizationData,
126            Ticket[] additionalTickets,
127            EncryptionKey subKey) throws KrbException, IOException {
128        this(options, asCreds, asCreds.getClient(), sname,
129                from, till, rtime, eTypes, addresses,
130                authorizationData, additionalTickets, subKey, null);
131    }
132
133    private KrbTgsReq(
134            KDCOptions options,
135            Credentials asCreds,
136            PrincipalName cname,
137            PrincipalName sname,
138            KerberosTime from,
139            KerberosTime till,
140            KerberosTime rtime,
141            int[] eTypes,
142            HostAddresses addresses,
143            AuthorizationData authorizationData,
144            Ticket[] additionalTickets,
145            EncryptionKey subKey,
146            PAData extraPA) throws KrbException, IOException {
147
148        princName = cname;
149        servName = sname;
150        ctime = KerberosTime.now();
151
152        // check if they are valid arguments. The optional fields
153        // should be consistent with settings in KDCOptions.
154
155        if (options.get(KDCOptions.FORWARDABLE) &&
156                (!(asCreds.flags.get(Krb5.TKT_OPTS_FORWARDABLE)))) {
157            options.set(KDCOptions.FORWARDABLE, false);
158        }
159        if (options.get(KDCOptions.FORWARDED)) {
160            if (!(asCreds.flags.get(KDCOptions.FORWARDABLE)))
161                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
162        }
163        if (options.get(KDCOptions.PROXIABLE) &&
164                (!(asCreds.flags.get(Krb5.TKT_OPTS_PROXIABLE)))) {
165            throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
166        }
167        if (options.get(KDCOptions.PROXY)) {
168            if (!(asCreds.flags.get(KDCOptions.PROXIABLE)))
169                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
170        }
171        if (options.get(KDCOptions.ALLOW_POSTDATE) &&
172                (!(asCreds.flags.get(Krb5.TKT_OPTS_MAY_POSTDATE)))) {
173            throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
174        }
175        if (options.get(KDCOptions.RENEWABLE) &&
176                (!(asCreds.flags.get(Krb5.TKT_OPTS_RENEWABLE)))) {
177            throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
178        }
179
180        if (options.get(KDCOptions.POSTDATED)) {
181            if (!(asCreds.flags.get(KDCOptions.POSTDATED)))
182                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
183        } else {
184            if (from != null)  from = null;
185        }
186        if (options.get(KDCOptions.RENEWABLE)) {
187            if (!(asCreds.flags.get(KDCOptions.RENEWABLE)))
188                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
189        } else {
190            if (rtime != null)  rtime = null;
191        }
192        if (options.get(KDCOptions.ENC_TKT_IN_SKEY) || options.get(KDCOptions.CNAME_IN_ADDL_TKT)) {
193            if (additionalTickets == null)
194                throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
195            // in TGS_REQ there could be more than one additional
196            // tickets,  but in file-based credential cache,
197            // there is only one additional ticket field.
198            secondTicket = additionalTickets[0];
199        } else {
200            if (additionalTickets != null)
201                additionalTickets = null;
202        }
203
204        tgsReqMessg = createRequest(
205                options,
206                asCreds.ticket,
207                asCreds.key,
208                ctime,
209                princName,
210                servName,
211                from,
212                till,
213                rtime,
214                eTypes,
215                addresses,
216                authorizationData,
217                additionalTickets,
218                subKey,
219                extraPA);
220        obuf = tgsReqMessg.asn1Encode();
221
222        // XXX We need to revisit this to see if can't move it
223        // up such that FORWARDED flag set in the options
224        // is included in the marshaled request.
225        /*
226         * If this is based on a forwarded ticket, record that in the
227         * options, because the returned TgsRep will contain the
228         * FORWARDED flag set.
229         */
230        if (asCreds.flags.get(KDCOptions.FORWARDED))
231            options.set(KDCOptions.FORWARDED, true);
232
233
234    }
235
236    /**
237     * Sends a TGS request to the realm of the target.
238     * @throws KrbException
239     * @throws IOException
240     */
241    public void send() throws IOException, KrbException {
242        String realmStr = null;
243        if (servName != null)
244            realmStr = servName.getRealmString();
245        KdcComm comm = new KdcComm(realmStr);
246        ibuf = comm.send(obuf);
247    }
248
249    public KrbTgsRep getReply()
250        throws KrbException, IOException {
251        return new KrbTgsRep(ibuf, this);
252    }
253
254    /**
255     * Sends the request, waits for a reply, and returns the Credentials.
256     * Used in Credentials, KrbCred, and internal/CredentialsUtil.
257     */
258    public Credentials sendAndGetCreds() throws IOException, KrbException {
259        KrbTgsRep tgs_rep = null;
260        String kdc = null;
261        send();
262        tgs_rep = getReply();
263        return tgs_rep.getCreds();
264    }
265
266    KerberosTime getCtime() {
267        return ctime;
268    }
269
270    private TGSReq createRequest(
271                         KDCOptions kdc_options,
272                         Ticket ticket,
273                         EncryptionKey key,
274                         KerberosTime ctime,
275                         PrincipalName cname,
276                         PrincipalName sname,
277                         KerberosTime from,
278                         KerberosTime till,
279                         KerberosTime rtime,
280                         int[] eTypes,
281                         HostAddresses addresses,
282                         AuthorizationData authorizationData,
283                         Ticket[] additionalTickets,
284                         EncryptionKey subKey,
285                         PAData extraPA)
286        throws IOException, KrbException, UnknownHostException {
287        KerberosTime req_till = null;
288        if (till == null) {
289            String d = Config.getInstance().get("libdefaults", "ticket_lifetime");
290            if (d != null) {
291                req_till = new KerberosTime(Instant.now().plusSeconds(Config.duration(d)));
292            } else {
293                req_till = new KerberosTime(0); // Choose KDC maximum allowed
294            }
295        } else {
296            req_till = till;
297        }
298
299        /*
300         * RFC 4120, Section 5.4.2.
301         * For KRB_TGS_REP, the ciphertext is encrypted in the
302         * sub-session key from the Authenticator, or if absent,
303         * the session key from the ticket-granting ticket used
304         * in the request.
305         *
306         * To support this, use tgsReqKey to remember which key to use.
307         */
308        tgsReqKey = key;
309
310        int[] req_eTypes = null;
311        if (eTypes == null) {
312            req_eTypes = EType.getDefaults("default_tgs_enctypes");
313        } else {
314            req_eTypes = eTypes;
315        }
316
317        EncryptionKey reqKey = null;
318        EncryptedData encAuthorizationData = null;
319        if (authorizationData != null) {
320            byte[] ad = authorizationData.asn1Encode();
321            if (subKey != null) {
322                reqKey = subKey;
323                tgsReqKey = subKey;    // Key to use to decrypt reply
324                useSubkey = true;
325                encAuthorizationData = new EncryptedData(reqKey, ad,
326                    KeyUsage.KU_TGS_REQ_AUTH_DATA_SUBKEY);
327            } else
328                encAuthorizationData = new EncryptedData(key, ad,
329                    KeyUsage.KU_TGS_REQ_AUTH_DATA_SESSKEY);
330        }
331
332        KDCReqBody reqBody = new KDCReqBody(
333                                            kdc_options,
334                                            cname,
335                                            sname,
336                                            from,
337                                            req_till,
338                                            rtime,
339                                            Nonce.value(),
340                                            req_eTypes,
341                                            addresses,
342                                            encAuthorizationData,
343                                            additionalTickets);
344
345        byte[] temp = reqBody.asn1Encode(Krb5.KRB_TGS_REQ);
346        // if the checksum type is one of the keyed checksum types,
347        // use session key.
348        Checksum cksum;
349        switch (Checksum.CKSUMTYPE_DEFAULT) {
350        case Checksum.CKSUMTYPE_RSA_MD4_DES:
351        case Checksum.CKSUMTYPE_DES_MAC:
352        case Checksum.CKSUMTYPE_DES_MAC_K:
353        case Checksum.CKSUMTYPE_RSA_MD4_DES_K:
354        case Checksum.CKSUMTYPE_RSA_MD5_DES:
355        case Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD:
356        case Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR:
357        case Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128:
358        case Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256:
359            cksum = new Checksum(Checksum.CKSUMTYPE_DEFAULT, temp, key,
360                KeyUsage.KU_PA_TGS_REQ_CKSUM);
361            break;
362        case Checksum.CKSUMTYPE_CRC32:
363        case Checksum.CKSUMTYPE_RSA_MD4:
364        case Checksum.CKSUMTYPE_RSA_MD5:
365        default:
366            cksum = new Checksum(Checksum.CKSUMTYPE_DEFAULT, temp);
367        }
368
369        // Usage will be KeyUsage.KU_PA_TGS_REQ_AUTHENTICATOR
370
371        byte[] tgs_ap_req = new KrbApReq(
372                                         new APOptions(),
373                                         ticket,
374                                         key,
375                                         cname,
376                                         cksum,
377                                         ctime,
378                                         reqKey,
379                                         null,
380                                         null).getMessage();
381
382        PAData tgsPAData = new PAData(Krb5.PA_TGS_REQ, tgs_ap_req);
383        return new TGSReq(
384                extraPA != null ?
385                    new PAData[] {extraPA, tgsPAData } :
386                    new PAData[] {tgsPAData},
387                reqBody);
388    }
389
390    TGSReq getMessage() {
391        return tgsReqMessg;
392    }
393
394    Ticket getSecondTicket() {
395        return secondTicket;
396    }
397
398    private static void debug(String message) {
399        //      System.err.println(">>> KrbTgsReq: " + message);
400    }
401
402    boolean usedSubkey() {
403        return useSubkey;
404    }
405
406}
407