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