1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3  *
4  * This code is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License version 2 only, as
6  * published by the Free Software Foundation.  Oracle designates this
7  * particular file as subject to the "Classpath" exception as provided
8  * by Oracle in the LICENSE file that accompanied this code.
9  *
10  * This code is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13  * version 2 for more details (a copy is included in the LICENSE file that
14  * accompanied this code).
15  *
16  * You should have received a copy of the GNU General Public License version
17  * 2 along with this work; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19  *
20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21  * or visit www.oracle.com if you need additional information or have any
22  * questions.
23  */
24 
25 /*
26  *
27  *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
28  *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
29  */
30 
31 package sun.security.krb5.internal.ccache;
32 
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.StringTokenizer;
38 
39 import sun.security.krb5.*;
40 import sun.security.krb5.internal.*;
41 import sun.security.krb5.internal.util.KrbDataInputStream;
42 import sun.security.util.IOUtils;
43 
44 /**
45  * This class extends KrbDataInputStream. It is used for parsing FCC-format
46  * data from file to memory.
47  *
48  * @author Yanni Zhang
49  *
50  */
51 public class CCacheInputStream extends KrbDataInputStream implements FileCCacheConstants {
52 
53     /*
54      * FCC version 2 contains type information for principals.  FCC
55      * version 1 does not.
56      *
57      * FCC version 3 contains keyblock encryption type information, and is
58      * architecture independent.  Previous versions are not.
59      *
60      * The code will accept version 1, 2, and 3 ccaches, and depending
61      * what KRB5_FCC_DEFAULT_FVNO is set to, it will create version 1, 2,
62      * or 3 FCC caches.
63      *
64      * The default credentials cache should be type 3 for now (see
65      * init_ctx.c).
66      */
67     /* V4 of the credentials cache format allows for header tags */
68 
69     private static boolean DEBUG = Krb5.DEBUG;
70 
CCacheInputStream(InputStream is)71     public CCacheInputStream(InputStream is){
72         super(is);
73     }
74 
75     /* Read tag field introduced in KRB5_FCC_FVNO_4 */
76     // this needs to be public for Kinit.
readTag()77     public Tag readTag() throws IOException {
78         char[] buf = new char[1024];
79         int len;
80         int tag = -1;
81         int taglen;
82         Integer time_offset = null;
83         Integer usec_offset = null;
84 
85         len = read(2);
86         if (len < 0) {
87             throw new IOException("stop.");
88         }
89         if (len > buf.length) {
90             throw new IOException("Invalid tag length.");
91         }
92         while (len > 0) {
93             tag    = read(2);
94             taglen = read(2);
95             switch (tag) {
96             case FCC_TAG_DELTATIME:
97                 time_offset = read(4);
98                 usec_offset = read(4);
99                 break;
100             default:
101             }
102             len = len - (4 + taglen);
103         }
104         return new Tag(len, tag, time_offset, usec_offset);
105     }
106     /*
107      * In file-based credential cache, the realm name is stored as part of
108      * principal name at the first place.
109      */
110     // made public for KinitOptions to call directly
readPrincipal(int version)111     public PrincipalName readPrincipal(int version) throws IOException, RealmException {
112         int type, length, namelength, kret;
113         String[] pname = null;
114         String realm;
115         /* Read principal type */
116         if (version == KRB5_FCC_FVNO_1) {
117             type = KRB5_NT_UNKNOWN;
118         } else {
119             type = read(4);
120         }
121         length = readLength4();
122         List<String> result = new ArrayList<String>();
123         /*
124          * DCE includes the principal's realm in the count; the new format
125          * does not.
126          */
127         if (version == KRB5_FCC_FVNO_1)
128             length--;
129         for (int i = 0; i <= length; i++) {
130             namelength = readLength4();
131             byte[] bytes = IOUtils.readFully(this, namelength, true);
132             result.add(new String(bytes));
133         }
134         if (result.isEmpty()) {
135             throw new IOException("No realm or principal");
136         }
137         if (isRealm(result.get(0))) {
138             realm = result.remove(0);
139             if (result.isEmpty()) {
140                 throw new IOException("No principal name components");
141             }
142             return new PrincipalName(
143                     type,
144                     result.toArray(new String[result.size()]),
145                     new Realm(realm));
146         }
147         try {
148             return new PrincipalName(
149                     type,
150                     result.toArray(new String[result.size()]),
151                     Realm.getDefault());
152         } catch (RealmException re) {
153             return null;
154         }
155     }
156 
157     /*
158      * In practice, a realm is named by uppercasing the DNS domain name. we currently
159      * rely on this to determine if the string within the principal identifier is realm
160      * name.
161      *
162      */
isRealm(String str)163     boolean isRealm(String str) {
164         try {
165             Realm r = new Realm(str);
166         }
167         catch (Exception e) {
168             return false;
169         }
170         StringTokenizer st = new StringTokenizer(str, ".");
171         String s;
172         while (st.hasMoreTokens()) {
173             s = st.nextToken();
174             for (int i = 0; i < s.length(); i++) {
175                 if (s.charAt(i) >= 141) {
176                     return false;
177                 }
178             }
179         }
180         return true;
181     }
182 
readKey(int version)183     EncryptionKey readKey(int version) throws IOException {
184         int keyType, keyLen;
185         keyType = read(2);
186         if (version == KRB5_FCC_FVNO_3)
187             read(2); /* keytype recorded twice in fvno 3 */
188         keyLen = readLength4();
189         byte[] bytes = IOUtils.readFully(this, keyLen, true);
190         return new EncryptionKey(bytes, keyType, version);
191     }
192 
readTimes()193     long[] readTimes() throws IOException {
194         long[] times = new long[4];
195         times[0] = (long)read(4) * 1000;
196         times[1] = (long)read(4) * 1000;
197         times[2] = (long)read(4) * 1000;
198         times[3] = (long)read(4) * 1000;
199         return times;
200     }
201 
readskey()202     boolean readskey() throws IOException {
203         if (read() == 0) {
204             return false;
205         }
206         else return true;
207     }
208 
readAddr()209     HostAddress[] readAddr() throws IOException, KrbApErrException {
210         int numAddrs, addrType, addrLength;
211         numAddrs = readLength4();
212         if (numAddrs > 0) {
213             List<HostAddress> addrs = new ArrayList<>();
214             for (int i = 0; i < numAddrs; i++) {
215                 addrType = read(2);
216                 addrLength = readLength4();
217                 if (!(addrLength == 4 || addrLength == 16)) {
218                     if (DEBUG) {
219                         System.out.println("Incorrect address format.");
220                     }
221                     return null;
222                 }
223                 byte[] result = new byte[addrLength];
224                 for (int j = 0; j < addrLength; j++)
225                     result[j] = (byte)read(1);
226                 addrs.add(new HostAddress(addrType, result));
227             }
228             return addrs.toArray(new HostAddress[addrs.size()]);
229         }
230         return null;
231     }
232 
readAuth()233     AuthorizationDataEntry[] readAuth() throws IOException {
234         int num, adtype, adlength;
235         num = readLength4();
236         if (num > 0) {
237             List<AuthorizationDataEntry> auData = new ArrayList<>();
238             byte[] data = null;
239             for (int i = 0; i < num; i++) {
240                 adtype = read(2);
241                 adlength = readLength4();
242                 data = IOUtils.readFully(this, adlength, true);
243                 auData.add(new AuthorizationDataEntry(adtype, data));
244             }
245             return auData.toArray(new AuthorizationDataEntry[auData.size()]);
246         }
247         else return null;
248     }
249 
readData()250     byte[] readData() throws IOException {
251         int length;
252         length = readLength4();
253         if (length == 0) {
254             return null;
255         } else {
256             return IOUtils.readFully(this, length, true);
257         }
258     }
259 
readFlags()260     boolean[] readFlags() throws IOException {
261         boolean[] flags = new boolean[Krb5.TKT_OPTS_MAX+1];
262         int ticketFlags;
263         ticketFlags = read(4);
264         if ((ticketFlags & 0x40000000) == TKT_FLG_FORWARDABLE)
265         flags[1] = true;
266         if ((ticketFlags & 0x20000000) == TKT_FLG_FORWARDED)
267         flags[2] = true;
268         if ((ticketFlags & 0x10000000) == TKT_FLG_PROXIABLE)
269         flags[3] = true;
270         if ((ticketFlags & 0x08000000) == TKT_FLG_PROXY)
271         flags[4] = true;
272         if ((ticketFlags & 0x04000000) == TKT_FLG_MAY_POSTDATE)
273         flags[5] = true;
274         if ((ticketFlags & 0x02000000) == TKT_FLG_POSTDATED)
275         flags[6] = true;
276         if ((ticketFlags & 0x01000000) == TKT_FLG_INVALID)
277         flags[7] = true;
278         if ((ticketFlags & 0x00800000) == TKT_FLG_RENEWABLE)
279         flags[8] = true;
280         if ((ticketFlags & 0x00400000) == TKT_FLG_INITIAL)
281         flags[9] = true;
282         if ((ticketFlags & 0x00200000) == TKT_FLG_PRE_AUTH)
283         flags[10] = true;
284         if ((ticketFlags & 0x00100000) == TKT_FLG_HW_AUTH)
285         flags[11] = true;
286         if (DEBUG) {
287             String msg = ">>> CCacheInputStream: readFlags() ";
288             if (flags[1] == true) {
289                 msg += " FORWARDABLE;";
290             }
291             if (flags[2] == true) {
292                 msg += " FORWARDED;";
293             }
294             if (flags[3] == true) {
295                 msg += " PROXIABLE;";
296             }
297             if (flags[4] == true) {
298                 msg += " PROXY;";
299             }
300             if (flags[5] == true) {
301                 msg += " MAY_POSTDATE;";
302             }
303             if (flags[6] == true) {
304                 msg += " POSTDATED;";
305             }
306             if (flags[7] == true) {
307                 msg += " INVALID;";
308             }
309             if (flags[8] == true) {
310                 msg += " RENEWABLE;";
311             }
312 
313             if (flags[9] == true) {
314                 msg += " INITIAL;";
315             }
316             if (flags[10] == true) {
317                 msg += " PRE_AUTH;";
318             }
319             if (flags[11] == true) {
320                 msg += " HW_AUTH;";
321             }
322             System.out.println(msg);
323         }
324         return flags;
325     }
326 
327     /**
328      * Reads the next cred in stream.
329      * @return the next cred, null if ticket or second_ticket unparseable.
330      *
331      * Note: MIT krb5 1.8.1 might generate a config entry with server principal
332      * X-CACHECONF:/krb5_ccache_conf_data/fast_avail/krbtgt/REALM@REALM. The
333      * entry is used by KDC to inform the client that it support certain
334      * features. Its ticket is not a valid krb5 ticket and thus this method
335      * returns null.
336      */
readCred(int version)337     Credentials readCred(int version) throws IOException,RealmException, KrbApErrException, Asn1Exception {
338         PrincipalName cpname = null;
339         try {
340             cpname = readPrincipal(version);
341         } catch (Exception e) {
342             // Do not return here. All data for this cred should be fully
343             // consumed so that we can read the next one.
344         }
345         if (DEBUG) {
346             System.out.println(">>>DEBUG <CCacheInputStream>  client principal is " + cpname);
347         }
348         PrincipalName spname = null;
349         try {
350             spname = readPrincipal(version);
351         } catch (Exception e) {
352             // same as above
353         }
354         if (DEBUG) {
355             System.out.println(">>>DEBUG <CCacheInputStream> server principal is " + spname);
356         }
357         EncryptionKey key = readKey(version);
358         if (DEBUG) {
359             System.out.println(">>>DEBUG <CCacheInputStream> key type: " + key.getEType());
360         }
361         long[] times = readTimes();
362         KerberosTime authtime = new KerberosTime(times[0]);
363         KerberosTime starttime =
364                 (times[1]==0) ? null : new KerberosTime(times[1]);
365         KerberosTime endtime = new KerberosTime(times[2]);
366         KerberosTime renewTill =
367                 (times[3]==0) ? null : new KerberosTime(times[3]);
368 
369         if (DEBUG) {
370             System.out.println(">>>DEBUG <CCacheInputStream> auth time: " + authtime.toDate().toString());
371             System.out.println(">>>DEBUG <CCacheInputStream> start time: " +
372                     ((starttime==null)?"null":starttime.toDate().toString()));
373             System.out.println(">>>DEBUG <CCacheInputStream> end time: " + endtime.toDate().toString());
374             System.out.println(">>>DEBUG <CCacheInputStream> renew_till time: " +
375                     ((renewTill==null)?"null":renewTill.toDate().toString()));
376         }
377         boolean skey = readskey();
378         boolean[] flags = readFlags();
379         TicketFlags tFlags = new TicketFlags(flags);
380         HostAddress[] addr = readAddr();
381         HostAddresses addrs = null;
382         if (addr != null) {
383             addrs = new HostAddresses(addr);
384         }
385         AuthorizationDataEntry[] auDataEntry = readAuth();
386         AuthorizationData auData = null;
387         if (auDataEntry != null) {
388             auData = new AuthorizationData(auDataEntry);
389         }
390         byte[] ticketData = readData();
391         byte[] ticketData2 = readData();
392 
393         // Skip this cred if either cpname or spname isn't created.
394         if (cpname == null || spname == null) {
395             return null;
396         }
397 
398         try {
399             return new Credentials(cpname, spname, key, authtime, starttime,
400                 endtime, renewTill, skey, tFlags,
401                 addrs, auData,
402                 ticketData != null ? new Ticket(ticketData) : null,
403                 ticketData2 != null ? new Ticket(ticketData2) : null);
404         } catch (Exception e) {     // If any of new Ticket(*) fails.
405             return null;
406         }
407     }
408 }
409