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
31package sun.security.krb5.internal.ccache;
32
33import java.io.IOException;
34import java.io.InputStream;
35import java.util.ArrayList;
36import java.util.List;
37import java.util.StringTokenizer;
38
39import sun.security.krb5.*;
40import sun.security.krb5.internal.*;
41import sun.security.krb5.internal.util.KrbDataInputStream;
42import 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 */
51public 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
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.
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
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     */
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
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
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
202    boolean readskey() throws IOException {
203        if (read() == 0) {
204            return false;
205        }
206        else return true;
207    }
208
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
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
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
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     */
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