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.misc.IOUtils;
  40 import sun.security.krb5.*;
  41 import sun.security.krb5.internal.*;
  42 import sun.security.krb5.internal.util.KrbDataInputStream;
  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 
  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 = new Integer(read(4));
  98                 usec_offset = new Integer(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 = read(4);
 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 = read(4);
 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                     result.toArray(new String[result.size()]),
 150                     type);
 151         } catch (RealmException re) {
 152             return null;
 153         }
 154     }
 155 
 156     /*
 157      * In practice, a realm is named by uppercasing the DNS domain name. we currently
 158      * rely on this to determine if the string within the principal identifier is realm
 159      * name.
 160      *
 161      */
 162     boolean isRealm(String str) {
 163         try {
 164             Realm r = new Realm(str);
 165         }
 166         catch (Exception e) {
 167             return false;
 168         }
 169         StringTokenizer st = new StringTokenizer(str, ".");
 170         String s;
 171         while (st.hasMoreTokens()) {
 172             s = st.nextToken();
 173             for (int i = 0; i < s.length(); i++) {
 174                 if (s.charAt(i) >= 141) {
 175                     return false;
 176                 }
 177             }
 178         }
 179         return true;
 180     }
 181 
 182     EncryptionKey readKey(int version) throws IOException {
 183         int keyType, keyLen;
 184         keyType = read(2);
 185         if (version == KRB5_FCC_FVNO_3)
 186             read(2); /* keytype recorded twice in fvno 3 */
 187         keyLen = read(4);
 188         byte[] bytes = IOUtils.readFully(this, keyLen, true);
 189         return new EncryptionKey(bytes, keyType, new Integer(version));
 190     }
 191 
 192     long[] readTimes() throws IOException {
 193         long[] times = new long[4];
 194         times[0] = (long)read(4) * 1000;
 195         times[1] = (long)read(4) * 1000;
 196         times[2] = (long)read(4) * 1000;
 197         times[3] = (long)read(4) * 1000;
 198         return times;
 199     }
 200 
 201     boolean readskey() throws IOException {
 202         if (read() == 0) {
 203             return false;
 204         }
 205         else return true;
 206     }
 207 
 208     HostAddress[] readAddr() throws IOException, KrbApErrException {
 209         int numAddrs, addrType, addrLength;
 210         numAddrs = read(4);
 211         if (numAddrs > 0) {
 212             List<HostAddress> addrs = new ArrayList<>();
 213             for (int i = 0; i < numAddrs; i++) {
 214                 addrType = read(2);
 215                 addrLength = read(4);
 216                 if (!(addrLength == 4 || addrLength == 16)) {
 217                     if (DEBUG) {
 218                         System.out.println("Incorrect address format.");
 219                     }
 220                     return null;
 221                 }
 222                 byte[] result = new byte[addrLength];
 223                 for (int j = 0; j < addrLength; j++)
 224                     result[j] = (byte)read(1);
 225                 addrs.add(new HostAddress(addrType, result));
 226             }
 227             return addrs.toArray(new HostAddress[addrs.size()]);
 228         }
 229         return null;
 230     }
 231 
 232     AuthorizationDataEntry[] readAuth() throws IOException {
 233         int num, adtype, adlength;
 234         num = read(4);
 235         if (num > 0) {
 236             List<AuthorizationDataEntry> auData = new ArrayList<>();
 237             byte[] data = null;
 238             for (int i = 0; i < num; i++) {
 239                 adtype = read(2);
 240                 adlength = read(4);
 241                 data = IOUtils.readFully(this, adlength, true);
 242                 auData.add(new AuthorizationDataEntry(adtype, data));
 243             }
 244             return auData.toArray(new AuthorizationDataEntry[auData.size()]);
 245         }
 246         else return null;
 247     }
 248 
 249     byte[] readData() throws IOException {
 250         int length;
 251         length = read(4);
 252         if (length == 0) {
 253             return null;
 254         } else {
 255             return IOUtils.readFully(this, length, true);
 256         }
 257     }
 258 
 259     boolean[] readFlags() throws IOException {
 260         boolean[] flags = new boolean[Krb5.TKT_OPTS_MAX+1];
 261         int ticketFlags;
 262         ticketFlags = read(4);
 263         if ((ticketFlags & 0x40000000) == TKT_FLG_FORWARDABLE)
 264         flags[1] = true;
 265         if ((ticketFlags & 0x20000000) == TKT_FLG_FORWARDED)
 266         flags[2] = true;
 267         if ((ticketFlags & 0x10000000) == TKT_FLG_PROXIABLE)
 268         flags[3] = true;
 269         if ((ticketFlags & 0x08000000) == TKT_FLG_PROXY)
 270         flags[4] = true;
 271         if ((ticketFlags & 0x04000000) == TKT_FLG_MAY_POSTDATE)
 272         flags[5] = true;
 273         if ((ticketFlags & 0x02000000) == TKT_FLG_POSTDATED)
 274         flags[6] = true;
 275         if ((ticketFlags & 0x01000000) == TKT_FLG_INVALID)
 276         flags[7] = true;
 277         if ((ticketFlags & 0x00800000) == TKT_FLG_RENEWABLE)
 278         flags[8] = true;
 279         if ((ticketFlags & 0x00400000) == TKT_FLG_INITIAL)
 280         flags[9] = true;
 281         if ((ticketFlags & 0x00200000) == TKT_FLG_PRE_AUTH)
 282         flags[10] = true;
 283         if ((ticketFlags & 0x00100000) == TKT_FLG_HW_AUTH)
 284         flags[11] = true;
 285         if (DEBUG) {
 286             String msg = ">>> CCacheInputStream: readFlags() ";
 287             if (flags[1] == true) {
 288                 msg += " FORWARDABLE;";
 289             }
 290             if (flags[2] == true) {
 291                 msg += " FORWARDED;";
 292             }
 293             if (flags[3] == true) {
 294                 msg += " PROXIABLE;";
 295             }
 296             if (flags[4] == true) {
 297                 msg += " PROXY;";
 298             }
 299             if (flags[5] == true) {
 300                 msg += " MAY_POSTDATE;";
 301             }
 302             if (flags[6] == true) {
 303                 msg += " POSTDATED;";
 304             }
 305             if (flags[7] == true) {
 306                 msg += " INVALID;";
 307             }
 308             if (flags[8] == true) {
 309                 msg += " RENEWABLE;";
 310             }
 311 
 312             if (flags[9] == true) {
 313                 msg += " INITIAL;";
 314             }
 315             if (flags[10] == true) {
 316                 msg += " PRE_AUTH;";
 317             }
 318             if (flags[11] == true) {
 319                 msg += " HW_AUTH;";
 320             }
 321             System.out.println(msg);
 322         }
 323         return flags;
 324     }
 325 
 326     /**
 327      * Reads the next cred in stream.
 328      * @return the next cred, null if ticket or second_ticket unparseable.
 329      *
 330      * Note: MIT krb5 1.8.1 might generate a config entry with server principal
 331      * X-CACHECONF:/krb5_ccache_conf_data/fast_avail/krbtgt/REALM@REALM. The
 332      * entry is used by KDC to inform the client that it support certain
 333      * features. Its ticket is not a valid krb5 ticket and thus this method
 334      * returns null.
 335      */
 336     Credentials readCred(int version) throws IOException,RealmException, KrbApErrException, Asn1Exception {
 337         PrincipalName cpname = null;
 338         try {
 339             cpname = readPrincipal(version);
 340         } catch (Exception e) {
 341             // cpname is null
 342         }
 343         if (DEBUG) {
 344             System.out.println(">>>DEBUG <CCacheInputStream>  client principal is " + cpname);
 345         }
 346         PrincipalName spname = null;
 347         try {
 348             spname = readPrincipal(version);
 349         } catch (Exception e) {
 350             // spname is null
 351         }
 352         if (DEBUG) {
 353             System.out.println(">>>DEBUG <CCacheInputStream> server principal is " + spname);
 354         }
 355         EncryptionKey key = readKey(version);
 356         if (DEBUG) {
 357             System.out.println(">>>DEBUG <CCacheInputStream> key type: " + key.getEType());
 358         }
 359         long times[] = readTimes();
 360         KerberosTime authtime = new KerberosTime(times[0]);
 361         KerberosTime starttime =
 362                 (times[1]==0) ? null : new KerberosTime(times[1]);
 363         KerberosTime endtime = new KerberosTime(times[2]);
 364         KerberosTime renewTill =
 365                 (times[3]==0) ? null : new KerberosTime(times[3]);
 366 
 367         if (DEBUG) {
 368             System.out.println(">>>DEBUG <CCacheInputStream> auth time: " + authtime.toDate().toString());
 369             System.out.println(">>>DEBUG <CCacheInputStream> start time: " +
 370                     ((starttime==null)?"null":starttime.toDate().toString()));
 371             System.out.println(">>>DEBUG <CCacheInputStream> end time: " + endtime.toDate().toString());
 372             System.out.println(">>>DEBUG <CCacheInputStream> renew_till time: " +
 373                     ((renewTill==null)?"null":renewTill.toDate().toString()));
 374         }
 375         boolean skey = readskey();
 376         boolean flags[] = readFlags();
 377         TicketFlags tFlags = new TicketFlags(flags);
 378         HostAddress addr[] = readAddr();
 379         HostAddresses addrs = null;
 380         if (addr != null) {
 381             addrs = new HostAddresses(addr);
 382         }
 383         AuthorizationDataEntry[] auDataEntry = readAuth();
 384         AuthorizationData auData = null;
 385         if (auDataEntry != null) {
 386             auData = new AuthorizationData(auDataEntry);
 387         }
 388         byte[] ticketData = readData();
 389         byte[] ticketData2 = readData();
 390 
 391         if (cpname == null || spname == null) {
 392             return null;
 393         }
 394 
 395         try {
 396             return new Credentials(cpname, spname, key, authtime, starttime,
 397                 endtime, renewTill, skey, tFlags,
 398                 addrs, auData,
 399                 ticketData != null ? new Ticket(ticketData) : null,
 400                 ticketData2 != null ? new Ticket(ticketData2) : null);
 401         } catch (Exception e) {     // If any of new Ticket(*) fails.
 402             return null;
 403         }
 404     }
 405 }