1 /* 2 * Copyright (c) 2000, 2010, 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.internal.ktab; 33 34 import sun.security.krb5.*; 35 import sun.security.krb5.internal.*; 36 import sun.security.krb5.internal.crypto.*; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.io.IOException; 40 import java.io.FileInputStream; 41 import java.io.FileOutputStream; 42 import java.io.File; 43 import java.util.Comparator; 44 import java.util.HashMap; 45 import java.util.Map; 46 import java.util.StringTokenizer; 47 import java.util.Vector; 48 49 /** 50 * This class represents key table. The key table functions deal with storing 51 * and retrieving service keys for use in authentication exchanges. 52 * 53 * @author Yanni Zhang 54 */ 55 public class KeyTab implements KeyTabConstants { 56 int kt_vno; 57 private static KeyTab singleton = null; 58 private static final boolean DEBUG = Krb5.DEBUG; 59 private static String name; 60 private Vector<KeyTabEntry> entries = new Vector<>(); 61 62 private KeyTab(String filename) throws IOException, RealmException { 63 init(filename); 64 } 65 66 public static KeyTab getInstance(String s) { 67 name = parse(s); 68 if (name == null) { 69 return getInstance(); 70 } 71 return getInstance(new File(name)); 72 } 73 74 /** 75 * Gets the single instance of KeyTab class. 76 * @param file the key tab file. 77 * @return single instance of KeyTab; 78 * return null if error occurs while reading data out of the file. 79 */ 80 public static KeyTab getInstance(File file) { 81 try { 82 if (!(file.exists())) { 83 singleton = null; 84 } else { 85 String fname = file.getAbsolutePath(); 86 // Since this class deals with file I/O operations, 87 // we want only one class instance existing. 88 if (singleton != null) { 89 File kfile = new File(singleton.name); 90 String kname = kfile.getAbsolutePath(); 91 if (kname.equalsIgnoreCase(fname)) { 92 if (DEBUG) { 93 System.out.println("KeyTab instance already exists"); 94 } 95 } 96 } else { 97 singleton = new KeyTab(fname); 98 } 99 } 100 } catch (Exception e) { 101 singleton = null; 102 if (DEBUG) { 103 System.out.println("Could not obtain an instance of KeyTab" + 104 e.getMessage()); 105 } 106 } 107 return singleton; 108 } 109 110 /** 111 * Gets the single instance of KeyTab class. 112 * @return single instance of KeyTab; return null if default keytab file 113 * does not exist, or error occurs while reading data from the file. 114 */ 115 public static KeyTab getInstance() { 116 try { 117 name = getDefaultKeyTab(); 118 if (name != null) { 119 singleton = getInstance(new File(name)); 120 } 121 } catch (Exception e) { 122 singleton = null; 123 if (DEBUG) { 124 System.out.println("Could not obtain an instance of KeyTab" + 125 e.getMessage()); 126 } 127 } 128 return singleton; 129 } 130 131 /** 132 * The location of keytab file will be read from the configuration file 133 * If it is not specified, consider user.home as the keytab file's 134 * default location. 135 */ 136 private static String getDefaultKeyTab() { 137 if (name != null) { 138 return name; 139 } else { 140 String kname = null; 141 try { 142 String keytab_names = Config.getInstance().getDefault 143 ("default_keytab_name", "libdefaults"); 144 if (keytab_names != null) { 145 StringTokenizer st = new StringTokenizer(keytab_names, " "); 146 while (st.hasMoreTokens()) { 147 kname = parse(st.nextToken()); 148 if (kname != null) { 149 break; 150 } 151 } 152 } 153 } catch (KrbException e) { 154 kname = null; 155 } 156 157 if (kname == null) { 158 String user_home = 159 java.security.AccessController.doPrivileged( 160 new sun.security.action.GetPropertyAction("user.home")); 161 162 if (user_home == null) { 163 user_home = 164 java.security.AccessController.doPrivileged( 165 new sun.security.action.GetPropertyAction("user.dir")); 166 } 167 168 if (user_home != null) { 169 kname = user_home + File.separator + "krb5.keytab"; 170 } 171 } 172 return kname; 173 } 174 } 175 176 private static String parse(String name) { 177 String kname = null; 178 if (name == null) { 179 return null; 180 } 181 if ((name.length() >= 5) && 182 (name.substring(0, 5).equalsIgnoreCase("FILE:"))) { 183 kname = name.substring(5); 184 } else if ((name.length() >= 9) && 185 (name.substring(0, 9).equalsIgnoreCase("ANY:FILE:"))) { 186 // this format found in MIT's krb5.ini. 187 kname = name.substring(9); 188 } else if ((name.length() >= 7) && 189 (name.substring(0, 7).equalsIgnoreCase("SRVTAB:"))) { 190 // this format found in MIT's krb5.ini. 191 kname = name.substring(7); 192 } else 193 kname = name; 194 return kname; 195 } 196 197 private synchronized void init(String filename) 198 throws IOException, RealmException { 199 200 if (filename != null) { 201 KeyTabInputStream kis = 202 new KeyTabInputStream(new FileInputStream(filename)); 203 load(kis); 204 kis.close(); 205 name = filename; 206 } 207 } 208 209 private void load(KeyTabInputStream kis) 210 throws IOException, RealmException { 211 212 entries.clear(); 213 kt_vno = kis.readVersion(); 214 if (kt_vno == KRB5_KT_VNO_1) { 215 kis.setNativeByteOrder(); 216 } 217 int entryLength = 0; 218 KeyTabEntry entry; 219 while (kis.available() > 0) { 220 entryLength = kis.readEntryLength(); 221 entry = kis.readEntry(entryLength, kt_vno); 222 if (DEBUG) { 223 System.out.println(">>> KeyTab: load() entry length: " + 224 entryLength + "; type: " + 225 (entry != null? entry.keyType : 0)); 226 } 227 if (entry != null) 228 entries.addElement(entry); 229 } 230 } 231 232 /** 233 * Reads all keys for a service from the keytab file that have 234 * etypes that have been configured for use. If there are multiple 235 * keys with same etype, the one with the highest kvno is returned. 236 * @param service the PrincipalName of the requested service 237 * @return an array containing all the service keys 238 */ 239 public EncryptionKey[] readServiceKeys(PrincipalName service) { 240 KeyTabEntry entry; 241 EncryptionKey key; 242 int size = entries.size(); 243 ArrayList<EncryptionKey> keys = new ArrayList<>(size); 244 245 for (int i = size-1; i >= 0; i--) { 246 entry = entries.elementAt(i); 247 if (entry.service.match(service)) { 248 if (EType.isSupported(entry.keyType)) { 249 key = new EncryptionKey(entry.keyblock, 250 entry.keyType, 251 new Integer(entry.keyVersion)); 252 keys.add(key); 253 if (DEBUG) { 254 System.out.println("Added key: " + entry.keyType + 255 "version: " + entry.keyVersion); 256 } 257 } else if (DEBUG) { 258 System.out.println("Found unsupported keytype (" + 259 entry.keyType + ") for " + service); 260 } 261 } 262 } 263 264 size = keys.size(); 265 if (size == 0) 266 return null; 267 EncryptionKey[] retVal = keys.toArray(new EncryptionKey[size]); 268 269 // Sort keys according to default_tkt_enctypes 270 if (DEBUG) { 271 System.out.println("Ordering keys wrt default_tkt_enctypes list"); 272 } 273 274 final int[] etypes = EType.getDefaults("default_tkt_enctypes"); 275 276 // Sort the keys, k1 is preferred than k2 if: 277 // 1. k1's etype appears earlier in etypes than k2's 278 // 2. If same, k1's KVNO is higher 279 Arrays.sort(retVal, new Comparator<EncryptionKey>() { 280 @Override 281 public int compare(EncryptionKey o1, EncryptionKey o2) { 282 if (etypes != null) { 283 int o1EType = o1.getEType(); 284 int o2EType = o2.getEType(); 285 if (o1EType != o2EType) { 286 for (int i=0; i<etypes.length; i++) { 287 if (etypes[i] == o1EType) { 288 return -1; 289 } else if (etypes[i] == o2EType) { 290 return 1; 291 } 292 } 293 // Neither o1EType nor o2EType in default_tkt_enctypes, 294 // therefore won't be used in AS-REQ. We do not care 295 // about their order, use kvno is OK. 296 } 297 } 298 return o2.getKeyVersionNumber().intValue() 299 - o1.getKeyVersionNumber().intValue(); 300 } 301 }); 302 303 return retVal; 304 } 305 306 307 308 /** 309 * Searches for the service entry in the keytab file. 310 * The etype of the key must be one that has been configured 311 * to be used. 312 * @param service the PrincipalName of the requested service. 313 * @return true if the entry is found, otherwise, return false. 314 */ 315 public boolean findServiceEntry(PrincipalName service) { 316 KeyTabEntry entry; 317 for (int i = 0; i < entries.size(); i++) { 318 entry = entries.elementAt(i); 319 if (entry.service.match(service)) { 320 if (EType.isSupported(entry.keyType)) { 321 return true; 322 } else if (DEBUG) { 323 System.out.println("Found unsupported keytype (" + 324 entry.keyType + ") for " + service); 325 } 326 } 327 } 328 return false; 329 } 330 331 public static String tabName() { 332 return name; 333 } 334 335 /** 336 * Adds a new entry in the key table. 337 * @param service the service which will have a new entry in the key table. 338 * @param psswd the password which generates the key. 339 * @param kvno the kvno to use, -1 means automatic increasing 340 * @param append false if entries with old kvno would be removed. 341 * Note: if kvno is not -1, entries with the same kvno are always removed 342 */ 343 public void addEntry(PrincipalName service, char[] psswd, 344 int kvno, boolean append) throws KrbException { 345 346 EncryptionKey[] encKeys = EncryptionKey.acquireSecretKeys( 347 psswd, service.getSalt()); 348 349 // There should be only one maximum KVNO value for all etypes, so that 350 // all added keys can have the same KVNO. 351 352 int maxKvno = 0; // only useful when kvno == -1 353 for (int i = entries.size()-1; i >= 0; i--) { 354 KeyTabEntry e = entries.get(i); 355 if (e.service.match(service)) { 356 if (e.keyVersion > maxKvno) { 357 maxKvno = e.keyVersion; 358 } 359 if (!append || e.keyVersion == kvno) { 360 entries.removeElementAt(i); 361 } 362 } 363 } 364 if (kvno == -1) { 365 kvno = maxKvno + 1; 366 } 367 368 for (int i = 0; encKeys != null && i < encKeys.length; i++) { 369 int keyType = encKeys[i].getEType(); 370 byte[] keyValue = encKeys[i].getBytes(); 371 372 KeyTabEntry newEntry = new KeyTabEntry(service, 373 service.getRealm(), 374 new KerberosTime(System.currentTimeMillis()), 375 kvno, keyType, keyValue); 376 entries.addElement(newEntry); 377 } 378 } 379 380 /** 381 * Gets the list of service entries in key table. 382 * @return array of <code>KeyTabEntry</code>. 383 */ 384 public KeyTabEntry[] getEntries() { 385 KeyTabEntry[] kentries = new KeyTabEntry[entries.size()]; 386 for (int i = 0; i < kentries.length; i++) { 387 kentries[i] = entries.elementAt(i); 388 } 389 return kentries; 390 } 391 392 /** 393 * Creates a new default key table. 394 */ 395 public synchronized static KeyTab create() 396 throws IOException, RealmException { 397 String dname = getDefaultKeyTab(); 398 return create(dname); 399 } 400 401 /** 402 * Creates a new default key table. 403 */ 404 public synchronized static KeyTab create(String name) 405 throws IOException, RealmException { 406 407 KeyTabOutputStream kos = 408 new KeyTabOutputStream(new FileOutputStream(name)); 409 kos.writeVersion(KRB5_KT_VNO); 410 kos.close(); 411 singleton = new KeyTab(name); 412 return singleton; 413 } 414 415 /** 416 * Saves the file at the directory. 417 */ 418 public synchronized void save() throws IOException { 419 KeyTabOutputStream kos = 420 new KeyTabOutputStream(new FileOutputStream(name)); 421 kos.writeVersion(kt_vno); 422 for (int i = 0; i < entries.size(); i++) { 423 kos.writeEntry(entries.elementAt(i)); 424 } 425 kos.close(); 426 } 427 428 /** 429 * Removes entries from the key table. 430 * @param service the service <code>PrincipalName</code>. 431 * @param etype the etype to match, remove all if -1 432 * @param kvno what kvno to remove, -1 for all, -2 for old 433 * @return the number of entries deleted 434 */ 435 public int deleteEntries(PrincipalName service, int etype, int kvno) { 436 int count = 0; 437 438 // Remember the highest KVNO for each etype. Used for kvno == -2 439 Map<Integer,Integer> highest = new HashMap<>(); 440 441 for (int i = entries.size()-1; i >= 0; i--) { 442 KeyTabEntry e = entries.get(i); 443 if (service.match(e.getService())) { 444 if (etype == -1 || e.keyType == etype) { 445 if (kvno == -2) { 446 // Two rounds for kvno == -2. In the first round (here), 447 // only find out highest KVNO for each etype 448 if (highest.containsKey(e.keyType)) { 449 int n = highest.get(e.keyType); 450 if (e.keyVersion > n) { 451 highest.put(e.keyType, e.keyVersion); 452 } 453 } else { 454 highest.put(e.keyType, e.keyVersion); 455 } 456 } else if (kvno == -1 || e.keyVersion == kvno) { 457 entries.removeElementAt(i); 458 count++; 459 } 460 } 461 } 462 } 463 464 // Second round for kvno == -2, remove old entries 465 if (kvno == -2) { 466 for (int i = entries.size()-1; i >= 0; i--) { 467 KeyTabEntry e = entries.get(i); 468 if (service.match(e.getService())) { 469 if (etype == -1 || e.keyType == etype) { 470 int n = highest.get(e.keyType); 471 if (e.keyVersion != n) { 472 entries.removeElementAt(i); 473 count++; 474 } 475 } 476 } 477 } 478 } 479 return count; 480 } 481 482 /** 483 * Creates key table file version. 484 * @param file the key table file. 485 * @exception IOException. 486 */ 487 public synchronized void createVersion(File file) throws IOException { 488 KeyTabOutputStream kos = 489 new KeyTabOutputStream(new FileOutputStream(file)); 490 kos.write16(KRB5_KT_VNO); 491 kos.close(); 492 } 493 494 public static void refresh() { 495 if (singleton != null) { 496 if (DEBUG) { 497 System.out.println("Refreshing Keytab"); 498 } 499 singleton = null; 500 } 501 } 502 }