1 /* 2 * Copyright (c) 2008, 2018, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.lang.reflect.Constructor; 25 import java.lang.reflect.Field; 26 import java.lang.reflect.InvocationTargetException; 27 import java.net.*; 28 import java.io.*; 29 import java.lang.reflect.Method; 30 import java.nio.file.Files; 31 import java.nio.file.Paths; 32 import java.util.*; 33 import java.util.concurrent.*; 34 import java.util.stream.Collectors; 35 import java.util.stream.Stream; 36 37 import sun.security.krb5.*; 38 import sun.security.krb5.internal.*; 39 import sun.security.krb5.internal.ccache.CredentialsCache; 40 import sun.security.krb5.internal.crypto.EType; 41 import sun.security.krb5.internal.crypto.KeyUsage; 42 import sun.security.krb5.internal.ktab.KeyTab; 43 import sun.security.util.DerInputStream; 44 import sun.security.util.DerOutputStream; 45 import sun.security.util.DerValue; 46 47 /** 48 * A KDC server. 49 * 50 * Note: By setting the system property native.kdc.path to a native 51 * krb5 installation, this class starts a native KDC with the 52 * given realm and host. It can also add new principals and save keytabs. 53 * Other features might not be available. 54 * <p> 55 * Features: 56 * <ol> 57 * <li> Supports TCP and UDP 58 * <li> Supports AS-REQ and TGS-REQ 59 * <li> Principal db and other settings hard coded in application 60 * <li> Options, say, request preauth or not 61 * </ol> 62 * Side effects: 63 * <ol> 64 * <li> The Sun-internal class <code>sun.security.krb5.Config</code> is a 65 * singleton and initialized according to Kerberos settings (krb5.conf and 66 * java.security.krb5.* system properties). This means once it's initialized 67 * it will not automatically notice any changes to these settings (or file 68 * changes of krb5.conf). The KDC class normally does not touch these 69 * settings (except for the <code>writeKtab()</code> method). However, to make 70 * sure nothing ever goes wrong, if you want to make any changes to these 71 * settings after calling a KDC method, call <code>Config.refresh()</code> to 72 * make sure your changes are reflected in the <code>Config</code> object. 73 * </ol> 74 * System properties recognized: 75 * <ul> 76 * <li>test.kdc.save.ccache 77 * </ul> 78 * Issues and TODOs: 79 * <ol> 80 * <li> Generates krb5.conf to be used on another machine, currently the kdc is 81 * always localhost 82 * <li> More options to KDC, say, error output, say, response nonce != 83 * request nonce 84 * </ol> 85 * Note: This program uses internal krb5 classes (including reflection to 86 * access private fields and methods). 87 * <p> 88 * Usages: 89 * <p> 90 * 1. Init and start the KDC: 91 * <pre> 92 * KDC kdc = KDC.create("REALM.NAME", port, isDaemon); 93 * KDC kdc = KDC.create("REALM.NAME"); 94 * </pre> 95 * Here, <code>port</code> is the UDP and TCP port number the KDC server 96 * listens on. If zero, a random port is chosen, which you can use getPort() 97 * later to retrieve the value. 98 * <p> 99 * If <code>isDaemon</code> is true, the KDC worker threads will be daemons. 100 * <p> 101 * The shortcut <code>KDC.create("REALM.NAME")</code> has port=0 and 102 * isDaemon=false, and is commonly used in an embedded KDC. 103 * <p> 104 * 2. Adding users: 105 * <pre> 106 * kdc.addPrincipal(String principal_name, char[] password); 107 * kdc.addPrincipalRandKey(String principal_name); 108 * </pre> 109 * A service principal's name should look like "host/f.q.d.n". The second form 110 * generates a random key. To expose this key, call <code>writeKtab()</code> to 111 * save the keys into a keytab file. 112 * <p> 113 * Note that you need to add the principal name krbtgt/REALM.NAME yourself. 114 * <p> 115 * Note that you can safely add a principal at any time after the KDC is 116 * started and before a user requests info on this principal. 117 * <p> 118 * 3. Other public methods: 119 * <ul> 120 * <li> <code>getPort</code>: Returns the port number the KDC uses 121 * <li> <code>getRealm</code>: Returns the realm name 122 * <li> <code>writeKtab</code>: Writes all principals' keys into a keytab file 123 * <li> <code>saveConfig</code>: Saves a krb5.conf file to access this KDC 124 * <li> <code>setOption</code>: Sets various options 125 * </ul> 126 * Read the javadoc for details. Lazy developer can use <code>OneKDC</code> 127 * directly. 128 */ 129 public class KDC { 130 131 public static final int DEFAULT_LIFETIME = 39600; 132 public static final int DEFAULT_RENEWTIME = 86400; 133 134 public static final String NOT_EXISTING_HOST = "not.existing.host"; 135 136 // What etypes the KDC supports. Comma-separated strings. Null for all. 137 // Please note native KDCs might use different names. 138 private static final String SUPPORTED_ETYPES 139 = System.getProperty("kdc.supported.enctypes"); 140 141 // The native KDC 142 private final NativeKdc nativeKdc; 143 144 // The native KDC process 145 private Process kdcProc = null; 146 147 // Under the hood. 148 149 // Principal db. principal -> pass. A case-insensitive TreeMap is used 150 // so that even if the client provides a name with different case, the KDC 151 // can still locate the principal and give back correct salt. 152 private TreeMap<String,char[]> passwords = new TreeMap<> 153 (String.CASE_INSENSITIVE_ORDER); 154 155 // Non default salts. Precisely, there should be different salts for 156 // different etypes, pretend they are the same at the moment. 157 private TreeMap<String,String> salts = new TreeMap<> 158 (String.CASE_INSENSITIVE_ORDER); 159 160 // Non default s2kparams for newer etypes. Precisely, there should be 161 // different s2kparams for different etypes, pretend they are the same 162 // at the moment. 163 private TreeMap<String,byte[]> s2kparamses = new TreeMap<> 164 (String.CASE_INSENSITIVE_ORDER); 165 166 // Realm name 167 private String realm; 168 // KDC 169 private String kdc; 170 // Service port number 171 private int port; 172 // The request/response job queue 173 private BlockingQueue<Job> q = new ArrayBlockingQueue<>(100); 174 // Options 175 private Map<Option,Object> options = new HashMap<>(); 176 // Realm-specific krb5.conf settings 177 private List<String> conf = new ArrayList<>(); 178 179 private Thread thread1, thread2, thread3; 180 private volatile boolean udpConsumerReady = false; 181 private volatile boolean tcpConsumerReady = false; 182 private volatile boolean dispatcherReady = false; 183 DatagramSocket u1 = null; 184 ServerSocket t1 = null; 185 186 public static enum KtabMode { APPEND, EXISTING }; 187 188 /** 189 * Option names, to be expanded forever. 190 */ 191 public static enum Option { 192 /** 193 * Whether pre-authentication is required. Default Boolean.TRUE 194 */ 195 PREAUTH_REQUIRED, 196 /** 197 * Only issue TGT in RC4 198 */ 199 ONLY_RC4_TGT, 200 /** 201 * Use RC4 as the first in preauth 202 */ 203 RC4_FIRST_PREAUTH, 204 /** 205 * Use only one preauth, so that some keys are not easy to generate 206 */ 207 ONLY_ONE_PREAUTH, 208 /** 209 * Set all name-type to a value in response 210 */ 211 RESP_NT, 212 /** 213 * Multiple ETYPE-INFO-ENTRY with same etype but different salt 214 */ 215 DUP_ETYPE, 216 /** 217 * What backend server can be delegated to 218 */ 219 OK_AS_DELEGATE, 220 /** 221 * Allow S4U2self, List<String> of middle servers. 222 * If not set, means KDC does not understand S4U2self at all, therefore 223 * would ignore any PA-FOR-USER request and send a ticket using the 224 * cname of teh requestor. If set, it returns FORWARDABLE tickets to 225 * a server with its name in the list 226 */ 227 ALLOW_S4U2SELF, 228 /** 229 * Allow S4U2proxy, Map<String,List<String>> of middle servers to 230 * backends. If not set or a backend not in a server's list, 231 * Krb5.KDC_ERR_POLICY will be send for S4U2proxy request. 232 */ 233 ALLOW_S4U2PROXY, 234 /** 235 * Sensitive accounts can never be delegated. 236 */ 237 SENSITIVE_ACCOUNTS, 238 /** 239 * If true, will check if TGS-REQ contains a non-null addresses field. 240 */ 241 CHECK_ADDRESSES, 242 }; 243 244 /** 245 * A standalone KDC server. 246 */ main(String[] args)247 public static void main(String[] args) throws Exception { 248 int port = args.length > 0 ? Integer.parseInt(args[0]) : 0; 249 KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", port, false); 250 kdc.addPrincipal("dummy", "bogus".toCharArray()); 251 kdc.addPrincipal("foo", "bar".toCharArray()); 252 kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE"); 253 kdc.addPrincipalRandKey("server/host.rabbit.hole"); 254 kdc.addPrincipalRandKey("backend/host.rabbit.hole"); 255 KDC.saveConfig("krb5.conf", kdc, "forwardable = true"); 256 } 257 258 /** 259 * Creates and starts a KDC running as a daemon on a random port. 260 * @param realm the realm name 261 * @return the running KDC instance 262 * @throws java.io.IOException for any socket creation error 263 */ create(String realm)264 public static KDC create(String realm) throws IOException { 265 return create(realm, "kdc." + realm.toLowerCase(Locale.US), 0, true); 266 } 267 existing(String realm, String kdc, int port)268 public static KDC existing(String realm, String kdc, int port) { 269 KDC k = new KDC(realm, kdc); 270 k.port = port; 271 return k; 272 } 273 274 /** 275 * Creates and starts a KDC server. 276 * @param realm the realm name 277 * @param port the TCP and UDP port to listen to. A random port will to 278 * chosen if zero. 279 * @param asDaemon if true, KDC threads will be daemons. Otherwise, not. 280 * @return the running KDC instance 281 * @throws java.io.IOException for any socket creation error 282 */ create(String realm, String kdc, int port, boolean asDaemon)283 public static KDC create(String realm, String kdc, int port, 284 boolean asDaemon) throws IOException { 285 return new KDC(realm, kdc, port, asDaemon); 286 } 287 288 /** 289 * Sets an option 290 * @param key the option name 291 * @param value the value 292 */ setOption(Option key, Object value)293 public void setOption(Option key, Object value) { 294 if (value == null) { 295 options.remove(key); 296 } else { 297 options.put(key, value); 298 } 299 } 300 301 /** 302 * Writes or appends keys into a keytab. 303 * <p> 304 * Attention: This is the most basic one of a series of methods below on 305 * keytab creation or modification. All these methods reference krb5.conf 306 * settings. If you need to modify krb5.conf or switch to another krb5.conf 307 * later, please call <code>Config.refresh()</code> again. For example: 308 * <pre> 309 * kdc.writeKtab("/etc/kdc/ktab", true); // Config is initialized, 310 * System.setProperty("java.security.krb5.conf", "/home/mykrb5.conf"); 311 * Config.refresh(); 312 * </pre> 313 * Inside this method there are 2 places krb5.conf is used: 314 * <ol> 315 * <li> (Fatal) Generating keys: EncryptionKey.acquireSecretKeys 316 * <li> (Has workaround) Creating PrincipalName 317 * </ol> 318 * @param tab the keytab file name 319 * @param append true if append, otherwise, overwrite. 320 * @param names the names to write into, write all if names is empty 321 */ writeKtab(String tab, boolean append, String... names)322 public void writeKtab(String tab, boolean append, String... names) 323 throws IOException, KrbException { 324 KeyTab ktab = null; 325 if (nativeKdc == null) { 326 ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab); 327 } 328 Iterable<String> entries = 329 (names.length != 0) ? Arrays.asList(names): passwords.keySet(); 330 for (String name : entries) { 331 if (name.indexOf('@') < 0) { 332 name = name + "@" + realm; 333 } 334 if (nativeKdc == null) { 335 char[] pass = passwords.get(name); 336 int kvno = 0; 337 if (Character.isDigit(pass[pass.length - 1])) { 338 kvno = pass[pass.length - 1] - '0'; 339 } 340 PrincipalName pn = new PrincipalName(name, 341 name.indexOf('/') < 0 ? 342 PrincipalName.KRB_NT_UNKNOWN : 343 PrincipalName.KRB_NT_SRV_HST); 344 ktab.addEntry(pn, 345 getSalt(pn), 346 pass, 347 kvno, 348 true); 349 } else { 350 nativeKdc.ktadd(name, tab); 351 } 352 } 353 if (nativeKdc == null) { 354 ktab.save(); 355 } 356 } 357 358 /** 359 * Writes all principals' keys from multiple KDCs into one keytab file. 360 * @throws java.io.IOException for any file output error 361 * @throws sun.security.krb5.KrbException for any realm and/or principal 362 * name error. 363 */ writeMultiKtab(String tab, KDC... kdcs)364 public static void writeMultiKtab(String tab, KDC... kdcs) 365 throws IOException, KrbException { 366 KeyTab.create(tab).save(); // Empty the old keytab 367 appendMultiKtab(tab, kdcs); 368 } 369 370 /** 371 * Appends all principals' keys from multiple KDCs to one keytab file. 372 */ appendMultiKtab(String tab, KDC... kdcs)373 public static void appendMultiKtab(String tab, KDC... kdcs) 374 throws IOException, KrbException { 375 for (KDC kdc: kdcs) { 376 kdc.writeKtab(tab, true); 377 } 378 } 379 380 /** 381 * Write a ktab for this KDC. 382 */ writeKtab(String tab)383 public void writeKtab(String tab) throws IOException, KrbException { 384 writeKtab(tab, false); 385 } 386 387 /** 388 * Appends keys in this KDC to a ktab. 389 */ appendKtab(String tab)390 public void appendKtab(String tab) throws IOException, KrbException { 391 writeKtab(tab, true); 392 } 393 394 /** 395 * Adds a new principal to this realm with a given password. 396 * @param user the principal's name. For a service principal, use the 397 * form of host/f.q.d.n 398 * @param pass the password for the principal 399 */ addPrincipal(String user, char[] pass)400 public void addPrincipal(String user, char[] pass) { 401 addPrincipal(user, pass, null, null); 402 } 403 404 /** 405 * Adds a new principal to this realm with a given password. 406 * @param user the principal's name. For a service principal, use the 407 * form of host/f.q.d.n 408 * @param pass the password for the principal 409 * @param salt the salt, or null if a default value will be used 410 * @param s2kparams the s2kparams, or null if a default value will be used 411 */ addPrincipal( String user, char[] pass, String salt, byte[] s2kparams)412 public void addPrincipal( 413 String user, char[] pass, String salt, byte[] s2kparams) { 414 if (user.indexOf('@') < 0) { 415 user = user + "@" + realm; 416 } 417 if (nativeKdc != null) { 418 if (!user.equals("krbtgt/" + realm)) { 419 nativeKdc.addPrincipal(user, new String(pass)); 420 } 421 passwords.put(user, new char[0]); 422 } else { 423 passwords.put(user, pass); 424 if (salt != null) { 425 salts.put(user, salt); 426 } 427 if (s2kparams != null) { 428 s2kparamses.put(user, s2kparams); 429 } 430 } 431 } 432 433 /** 434 * Adds a new principal to this realm with a random password 435 * @param user the principal's name. For a service principal, use the 436 * form of host/f.q.d.n 437 */ addPrincipalRandKey(String user)438 public void addPrincipalRandKey(String user) { 439 addPrincipal(user, randomPassword()); 440 } 441 442 /** 443 * Returns the name of this realm 444 * @return the name of this realm 445 */ getRealm()446 public String getRealm() { 447 return realm; 448 } 449 450 /** 451 * Returns the name of kdc 452 * @return the name of kdc 453 */ getKDC()454 public String getKDC() { 455 return kdc; 456 } 457 458 /** 459 * Add realm-specific krb5.conf setting 460 */ addConf(String s)461 public void addConf(String s) { 462 conf.add(s); 463 } 464 465 /** 466 * Writes a krb5.conf for one or more KDC that includes KDC locations for 467 * each realm and the default realm name. You can also add extra strings 468 * into the file. The method should be called like: 469 * <pre> 470 * KDC.saveConfig("krb5.conf", kdc1, kdc2, ..., line1, line2, ...); 471 * </pre> 472 * Here you can provide one or more kdc# and zero or more line# arguments. 473 * The line# will be put after [libdefaults] and before [realms]. Therefore 474 * you can append new lines into [libdefaults] and/or create your new 475 * stanzas as well. Note that a newline character will be appended to 476 * each line# argument. 477 * <p> 478 * For example: 479 * <pre> 480 * KDC.saveConfig("krb5.conf", this); 481 * </pre> 482 * generates: 483 * <pre> 484 * [libdefaults] 485 * default_realm = REALM.NAME 486 * 487 * [realms] 488 * REALM.NAME = { 489 * kdc = host:port_number 490 * # realm-specific settings 491 * } 492 * </pre> 493 * 494 * Another example: 495 * <pre> 496 * KDC.saveConfig("krb5.conf", kdc1, kdc2, "forwardable = true", "", 497 * "[domain_realm]", 498 * ".kdc1.com = KDC1.NAME"); 499 * </pre> 500 * generates: 501 * <pre> 502 * [libdefaults] 503 * default_realm = KDC1.NAME 504 * forwardable = true 505 * 506 * [domain_realm] 507 * .kdc1.com = KDC1.NAME 508 * 509 * [realms] 510 * KDC1.NAME = { 511 * kdc = host:port1 512 * } 513 * KDC2.NAME = { 514 * kdc = host:port2 515 * } 516 * </pre> 517 * @param file the name of the file to write into 518 * @param kdc the first (and default) KDC 519 * @param more more KDCs or extra lines (in their appearing order) to 520 * insert into the krb5.conf file. This method reads each argument's type 521 * to determine what it's for. This argument can be empty. 522 * @throws java.io.IOException for any file output error 523 */ saveConfig(String file, KDC kdc, Object... more)524 public static void saveConfig(String file, KDC kdc, Object... more) 525 throws IOException { 526 StringBuffer sb = new StringBuffer(); 527 sb.append("[libdefaults]\ndefault_realm = "); 528 sb.append(kdc.realm); 529 sb.append("\n"); 530 for (Object o : more) { 531 if (o instanceof String) { 532 sb.append(o); 533 sb.append("\n"); 534 } 535 } 536 sb.append("\n[realms]\n"); 537 sb.append(kdc.realmLine()); 538 for (Object o : more) { 539 if (o instanceof KDC) { 540 sb.append(((KDC) o).realmLine()); 541 } 542 } 543 Files.write(Paths.get(file), sb.toString().getBytes()); 544 } 545 546 /** 547 * Returns the service port of the KDC server. 548 * @return the KDC service port 549 */ getPort()550 public int getPort() { 551 return port; 552 } 553 554 // Private helper methods 555 556 /** 557 * Private constructor, cannot be called outside. 558 * @param realm 559 */ KDC(String realm, String kdc)560 private KDC(String realm, String kdc) { 561 this.realm = realm; 562 this.kdc = kdc; 563 this.nativeKdc = null; 564 } 565 566 /** 567 * A constructor that starts the KDC service also. 568 */ KDC(String realm, String kdc, int port, boolean asDaemon)569 protected KDC(String realm, String kdc, int port, boolean asDaemon) 570 throws IOException { 571 this.realm = realm; 572 this.kdc = kdc; 573 this.nativeKdc = NativeKdc.get(this); 574 startServer(port, asDaemon); 575 } 576 /** 577 * Generates a 32-char random password 578 * @return the password 579 */ randomPassword()580 private static char[] randomPassword() { 581 char[] pass = new char[32]; 582 Random r = new Random(); 583 for (int i=0; i<31; i++) 584 pass[i] = (char)('a' + r.nextInt(26)); 585 // The last char cannot be a number, otherwise, keyForUser() 586 // believes it's a sign of kvno 587 pass[31] = 'Z'; 588 return pass; 589 } 590 591 /** 592 * Generates a random key for the given encryption type. 593 * @param eType the encryption type 594 * @return the generated key 595 * @throws sun.security.krb5.KrbException for unknown/unsupported etype 596 */ generateRandomKey(int eType)597 private static EncryptionKey generateRandomKey(int eType) 598 throws KrbException { 599 return genKey0(randomPassword(), "NOTHING", null, eType, null); 600 } 601 602 /** 603 * Returns the password for a given principal 604 * @param p principal 605 * @return the password 606 * @throws sun.security.krb5.KrbException when the principal is not inside 607 * the database. 608 */ getPassword(PrincipalName p, boolean server)609 private char[] getPassword(PrincipalName p, boolean server) 610 throws KrbException { 611 String pn = p.toString(); 612 if (p.getRealmString() == null) { 613 pn = pn + "@" + getRealm(); 614 } 615 char[] pass = passwords.get(pn); 616 if (pass == null) { 617 throw new KrbException(server? 618 Krb5.KDC_ERR_S_PRINCIPAL_UNKNOWN: 619 Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN, pn.toString()); 620 } 621 return pass; 622 } 623 624 /** 625 * Returns the salt string for the principal. 626 * @param p principal 627 * @return the salt 628 */ getSalt(PrincipalName p)629 protected String getSalt(PrincipalName p) { 630 String pn = p.toString(); 631 if (p.getRealmString() == null) { 632 pn = pn + "@" + getRealm(); 633 } 634 if (salts.containsKey(pn)) { 635 return salts.get(pn); 636 } 637 if (passwords.containsKey(pn)) { 638 try { 639 // Find the principal name with correct case. 640 p = new PrincipalName(passwords.ceilingEntry(pn).getKey()); 641 } catch (RealmException re) { 642 // Won't happen 643 } 644 } 645 String s = p.getRealmString(); 646 if (s == null) s = getRealm(); 647 for (String n: p.getNameStrings()) { 648 s += n; 649 } 650 return s; 651 } 652 653 /** 654 * Returns the s2kparams for the principal given the etype. 655 * @param p principal 656 * @param etype encryption type 657 * @return the s2kparams, might be null 658 */ getParams(PrincipalName p, int etype)659 protected byte[] getParams(PrincipalName p, int etype) { 660 switch (etype) { 661 case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: 662 case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: 663 case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128: 664 case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192: 665 String pn = p.toString(); 666 if (p.getRealmString() == null) { 667 pn = pn + "@" + getRealm(); 668 } 669 if (s2kparamses.containsKey(pn)) { 670 return s2kparamses.get(pn); 671 } 672 if (etype < EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128) { 673 return new byte[]{0, 0, 0x10, 0}; 674 } else { 675 return new byte[]{0, 0, (byte) 0x80, 0}; 676 } 677 default: 678 return null; 679 } 680 } 681 682 /** 683 * Returns the key for a given principal of the given encryption type 684 * @param p the principal 685 * @param etype the encryption type 686 * @param server looking for a server principal? 687 * @return the key 688 * @throws sun.security.krb5.KrbException for unknown/unsupported etype 689 */ keyForUser(PrincipalName p, int etype, boolean server)690 private EncryptionKey keyForUser(PrincipalName p, int etype, boolean server) 691 throws KrbException { 692 try { 693 // Do not call EncryptionKey.acquireSecretKeys(), otherwise 694 // the krb5.conf config file would be loaded. 695 Integer kvno = null; 696 // For service whose password ending with a number, use it as kvno. 697 // Kvno must be postive. 698 if (p.toString().indexOf('/') > 0) { 699 char[] pass = getPassword(p, server); 700 if (Character.isDigit(pass[pass.length-1])) { 701 kvno = pass[pass.length-1] - '0'; 702 } 703 } 704 return genKey0(getPassword(p, server), getSalt(p), 705 getParams(p, etype), etype, kvno); 706 } catch (KrbException ke) { 707 throw ke; 708 } catch (Exception e) { 709 throw new RuntimeException(e); // should not happen 710 } 711 } 712 713 /** 714 * Returns a KerberosTime. 715 * 716 * @param offset offset from NOW in seconds 717 */ timeAfter(int offset)718 private static KerberosTime timeAfter(int offset) { 719 return new KerberosTime(new Date().getTime() + offset * 1000L); 720 } 721 722 /** 723 * Generates key from password. 724 */ genKey0( char[] pass, String salt, byte[] s2kparams, int etype, Integer kvno)725 private static EncryptionKey genKey0( 726 char[] pass, String salt, byte[] s2kparams, 727 int etype, Integer kvno) throws KrbException { 728 return new EncryptionKey(EncryptionKeyDotStringToKey( 729 pass, salt, s2kparams, etype), 730 etype, kvno); 731 } 732 733 /** 734 * Processes an incoming request and generates a response. 735 * @param in the request 736 * @return the response 737 * @throws java.lang.Exception for various errors 738 */ processMessage(byte[] in)739 protected byte[] processMessage(byte[] in) throws Exception { 740 if ((in[0] & 0x1f) == Krb5.KRB_AS_REQ) 741 return processAsReq(in); 742 else 743 return processTgsReq(in); 744 } 745 746 /** 747 * Processes a TGS_REQ and generates a TGS_REP (or KRB_ERROR) 748 * @param in the request 749 * @return the response 750 * @throws java.lang.Exception for various errors 751 */ processTgsReq(byte[] in)752 protected byte[] processTgsReq(byte[] in) throws Exception { 753 TGSReq tgsReq = new TGSReq(in); 754 PrincipalName service = tgsReq.reqBody.sname; 755 if (options.containsKey(KDC.Option.RESP_NT)) { 756 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT), 757 service.getNameStrings(), service.getRealm()); 758 } 759 try { 760 System.out.println(realm + "> " + tgsReq.reqBody.cname + 761 " sends TGS-REQ for " + 762 service + ", " + tgsReq.reqBody.kdcOptions); 763 KDCReqBody body = tgsReq.reqBody; 764 int[] eTypes = filterSupported(KDCReqBodyDotEType(body)); 765 if (eTypes.length == 0) { 766 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 767 } 768 int e2 = eTypes[0]; // etype for outgoing session key 769 int e3 = eTypes[0]; // etype for outgoing ticket 770 771 PAData[] pas = KDCReqDotPAData(tgsReq); 772 773 Ticket tkt = null; 774 EncTicketPart etp = null; 775 776 PrincipalName cname = null; 777 boolean allowForwardable = true; 778 779 if (pas == null || pas.length == 0) { 780 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 781 } else { 782 PrincipalName forUserCName = null; 783 for (PAData pa: pas) { 784 if (pa.getType() == Krb5.PA_TGS_REQ) { 785 APReq apReq = new APReq(pa.getValue()); 786 EncryptedData ed = apReq.authenticator; 787 tkt = apReq.ticket; 788 int te = tkt.encPart.getEType(); 789 EncryptionKey kkey = keyForUser(tkt.sname, te, true); 790 byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET); 791 DerInputStream derIn = new DerInputStream(bb); 792 DerValue der = derIn.getDerValue(); 793 etp = new EncTicketPart(der.toByteArray()); 794 // Finally, cname will be overwritten by PA-FOR-USER 795 // if it exists. 796 cname = etp.cname; 797 System.out.println(realm + "> presenting a ticket of " 798 + etp.cname + " to " + tkt.sname); 799 } else if (pa.getType() == Krb5.PA_FOR_USER) { 800 if (options.containsKey(Option.ALLOW_S4U2SELF)) { 801 PAForUserEnc p4u = new PAForUserEnc( 802 new DerValue(pa.getValue()), null); 803 forUserCName = p4u.name; 804 System.out.println(realm + "> See PA_FOR_USER " 805 + " in the name of " + p4u.name); 806 } 807 } 808 } 809 if (forUserCName != null) { 810 List<String> names = (List<String>) 811 options.get(Option.ALLOW_S4U2SELF); 812 if (!names.contains(cname.toString())) { 813 // Mimic the normal KDC behavior. When a server is not 814 // allowed to send S4U2self, do not send an error. 815 // Instead, send a ticket which is useless later. 816 allowForwardable = false; 817 } 818 cname = forUserCName; 819 } 820 if (tkt == null) { 821 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 822 } 823 } 824 825 // Session key for original ticket, TGT 826 EncryptionKey ckey = etp.key; 827 828 // Session key for session with the service 829 EncryptionKey key = generateRandomKey(e2); 830 831 // Check time, TODO 832 KerberosTime from = body.from; 833 KerberosTime till = body.till; 834 if (from == null || from.isZero()) { 835 from = timeAfter(0); 836 } 837 if (till == null) { 838 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO 839 } else if (till.isZero()) { 840 till = timeAfter(DEFAULT_LIFETIME); 841 } 842 843 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; 844 if (body.kdcOptions.get(KDCOptions.FORWARDABLE) 845 && allowForwardable) { 846 List<String> sensitives = (List<String>) 847 options.get(Option.SENSITIVE_ACCOUNTS); 848 if (sensitives != null && sensitives.contains(cname.toString())) { 849 // Cannot make FORWARDABLE 850 } else { 851 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; 852 } 853 } 854 // We do not request for addresses for FORWARDED tickets 855 if (options.containsKey(Option.CHECK_ADDRESSES) 856 && body.kdcOptions.get(KDCOptions.FORWARDED) 857 && body.addresses != null) { 858 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 859 } 860 if (body.kdcOptions.get(KDCOptions.FORWARDED) || 861 etp.flags.get(Krb5.TKT_OPTS_FORWARDED)) { 862 bFlags[Krb5.TKT_OPTS_FORWARDED] = true; 863 } 864 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { 865 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 866 //renew = timeAfter(3600 * 24 * 7); 867 } 868 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { 869 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; 870 } 871 if (body.kdcOptions.get(KDCOptions.POSTDATED)) { 872 bFlags[Krb5.TKT_OPTS_POSTDATED] = true; 873 } 874 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { 875 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; 876 } 877 if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT)) { 878 if (!options.containsKey(Option.ALLOW_S4U2PROXY)) { 879 // Don't understand CNAME_IN_ADDL_TKT 880 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 881 } else { 882 Map<String,List<String>> map = (Map<String,List<String>>) 883 options.get(Option.ALLOW_S4U2PROXY); 884 Ticket second = KDCReqBodyDotFirstAdditionalTicket(body); 885 EncryptionKey key2 = keyForUser( 886 second.sname, second.encPart.getEType(), true); 887 byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET); 888 DerInputStream derIn = new DerInputStream(bb); 889 DerValue der = derIn.getDerValue(); 890 EncTicketPart tktEncPart = new EncTicketPart(der.toByteArray()); 891 if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)) { 892 //throw new KrbException(Krb5.KDC_ERR_BADOPTION); 893 } 894 PrincipalName client = tktEncPart.cname; 895 System.out.println(realm + "> and an additional ticket of " 896 + client + " to " + second.sname); 897 if (map.containsKey(cname.toString())) { 898 if (map.get(cname.toString()).contains(service.toString())) { 899 System.out.println(realm + "> S4U2proxy OK"); 900 } else { 901 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 902 } 903 } else { 904 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 905 } 906 cname = client; 907 } 908 } 909 910 String okAsDelegate = (String)options.get(Option.OK_AS_DELEGATE); 911 if (okAsDelegate != null && ( 912 okAsDelegate.isEmpty() || 913 okAsDelegate.contains(service.getNameString()))) { 914 bFlags[Krb5.TKT_OPTS_DELEGATE] = true; 915 } 916 bFlags[Krb5.TKT_OPTS_INITIAL] = true; 917 918 KerberosTime renewTill = etp.renewTill; 919 if (renewTill != null && body.kdcOptions.get(KDCOptions.RENEW)) { 920 // till should never pass renewTill 921 if (till.greaterThan(renewTill)) { 922 till = renewTill; 923 } 924 if (System.getProperty("test.set.null.renew") != null) { 925 // Testing 8186576, see NullRenewUntil.java. 926 renewTill = null; 927 } 928 } 929 930 TicketFlags tFlags = new TicketFlags(bFlags); 931 EncTicketPart enc = new EncTicketPart( 932 tFlags, 933 key, 934 cname, 935 new TransitedEncoding(1, new byte[0]), // TODO 936 timeAfter(0), 937 from, 938 till, renewTill, 939 body.addresses != null ? body.addresses 940 : etp.caddr, 941 null); 942 EncryptionKey skey = keyForUser(service, e3, true); 943 if (skey == null) { 944 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO 945 } 946 Ticket t = new Ticket( 947 System.getProperty("test.kdc.diff.sname") != null ? 948 new PrincipalName("xx" + service.toString()) : 949 service, 950 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET) 951 ); 952 EncTGSRepPart enc_part = new EncTGSRepPart( 953 key, 954 new LastReq(new LastReqEntry[] { 955 new LastReqEntry(0, timeAfter(-10)) 956 }), 957 body.getNonce(), // TODO: detect replay 958 timeAfter(3600 * 24), 959 // Next 5 and last MUST be same with ticket 960 tFlags, 961 timeAfter(0), 962 from, 963 till, renewTill, 964 service, 965 body.addresses 966 ); 967 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), 968 KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY); 969 TGSRep tgsRep = new TGSRep(null, 970 cname, 971 t, 972 edata); 973 System.out.println(" Return " + tgsRep.cname 974 + " ticket for " + tgsRep.ticket.sname + ", flags " 975 + tFlags); 976 977 DerOutputStream out = new DerOutputStream(); 978 out.write(DerValue.createTag(DerValue.TAG_APPLICATION, 979 true, (byte)Krb5.KRB_TGS_REP), tgsRep.asn1Encode()); 980 return out.toByteArray(); 981 } catch (KrbException ke) { 982 ke.printStackTrace(System.out); 983 KRBError kerr = ke.getError(); 984 KDCReqBody body = tgsReq.reqBody; 985 System.out.println(" Error " + ke.returnCode() 986 + " " +ke.returnCodeMessage()); 987 if (kerr == null) { 988 kerr = new KRBError(null, null, null, 989 timeAfter(0), 990 0, 991 ke.returnCode(), 992 body.cname, 993 service, 994 KrbException.errorMessage(ke.returnCode()), 995 null); 996 } 997 return kerr.asn1Encode(); 998 } 999 } 1000 1001 /** 1002 * Processes a AS_REQ and generates a AS_REP (or KRB_ERROR) 1003 * @param in the request 1004 * @return the response 1005 * @throws java.lang.Exception for various errors 1006 */ processAsReq(byte[] in)1007 protected byte[] processAsReq(byte[] in) throws Exception { 1008 ASReq asReq = new ASReq(in); 1009 int[] eTypes = null; 1010 List<PAData> outPAs = new ArrayList<>(); 1011 1012 PrincipalName service = asReq.reqBody.sname; 1013 if (options.containsKey(KDC.Option.RESP_NT)) { 1014 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT), 1015 service.getNameStrings(), 1016 Realm.getDefault()); 1017 } 1018 try { 1019 System.out.println(realm + "> " + asReq.reqBody.cname + 1020 " sends AS-REQ for " + 1021 service + ", " + asReq.reqBody.kdcOptions); 1022 1023 KDCReqBody body = asReq.reqBody; 1024 1025 eTypes = filterSupported(KDCReqBodyDotEType(body)); 1026 if (eTypes.length == 0) { 1027 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1028 } 1029 int eType = eTypes[0]; 1030 1031 EncryptionKey ckey = keyForUser(body.cname, eType, false); 1032 EncryptionKey skey = keyForUser(service, eType, true); 1033 1034 if (options.containsKey(KDC.Option.ONLY_RC4_TGT)) { 1035 int tgtEType = EncryptedData.ETYPE_ARCFOUR_HMAC; 1036 boolean found = false; 1037 for (int i=0; i<eTypes.length; i++) { 1038 if (eTypes[i] == tgtEType) { 1039 found = true; 1040 break; 1041 } 1042 } 1043 if (!found) { 1044 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1045 } 1046 skey = keyForUser(service, tgtEType, true); 1047 } 1048 if (ckey == null) { 1049 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1050 } 1051 if (skey == null) { 1052 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO 1053 } 1054 1055 // Session key 1056 EncryptionKey key = generateRandomKey(eType); 1057 // Check time, TODO 1058 KerberosTime from = body.from; 1059 KerberosTime till = body.till; 1060 KerberosTime rtime = body.rtime; 1061 if (from == null || from.isZero()) { 1062 from = timeAfter(0); 1063 } 1064 if (till == null) { 1065 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO 1066 } else if (till.isZero()) { 1067 till = timeAfter(DEFAULT_LIFETIME); 1068 } else if (till.greaterThan(timeAfter(24 * 3600)) 1069 && System.getProperty("test.kdc.force.till") == null) { 1070 // If till is more than 1 day later, make it renewable 1071 till = timeAfter(DEFAULT_LIFETIME); 1072 body.kdcOptions.set(KDCOptions.RENEWABLE, true); 1073 if (rtime == null) rtime = till; 1074 } 1075 if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) { 1076 rtime = timeAfter(DEFAULT_RENEWTIME); 1077 } 1078 //body.from 1079 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; 1080 if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) { 1081 List<String> sensitives = (List<String>) 1082 options.get(Option.SENSITIVE_ACCOUNTS); 1083 if (sensitives != null 1084 && sensitives.contains(body.cname.toString())) { 1085 // Cannot make FORWARDABLE 1086 } else { 1087 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; 1088 } 1089 } 1090 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { 1091 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 1092 //renew = timeAfter(3600 * 24 * 7); 1093 } 1094 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { 1095 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; 1096 } 1097 if (body.kdcOptions.get(KDCOptions.POSTDATED)) { 1098 bFlags[Krb5.TKT_OPTS_POSTDATED] = true; 1099 } 1100 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { 1101 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; 1102 } 1103 bFlags[Krb5.TKT_OPTS_INITIAL] = true; 1104 1105 // Creating PA-DATA 1106 DerValue[] pas2 = null, pas = null; 1107 if (options.containsKey(KDC.Option.DUP_ETYPE)) { 1108 int n = (Integer)options.get(KDC.Option.DUP_ETYPE); 1109 switch (n) { 1110 case 1: // customer's case in 7067974 1111 pas2 = new DerValue[] { 1112 new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), 1113 new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), 1114 new DerValue(new ETypeInfo2( 1115 1, realm, new byte[]{1}).asn1Encode()), 1116 }; 1117 pas = new DerValue[] { 1118 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1119 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1120 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1121 }; 1122 break; 1123 case 2: // we still reject non-null s2kparams and prefer E2 over E 1124 pas2 = new DerValue[] { 1125 new DerValue(new ETypeInfo2( 1126 1, realm, new byte[]{1}).asn1Encode()), 1127 new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), 1128 new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), 1129 }; 1130 pas = new DerValue[] { 1131 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1132 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1133 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1134 }; 1135 break; 1136 case 3: // but only E is wrong 1137 pas = new DerValue[] { 1138 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1139 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1140 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1141 }; 1142 break; 1143 case 4: // we also ignore rc4-hmac 1144 pas = new DerValue[] { 1145 new DerValue(new ETypeInfo(23, "ANYTHING").asn1Encode()), 1146 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1147 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1148 }; 1149 break; 1150 case 5: // "" should be wrong, but we accept it now 1151 // See s.s.k.internal.PAData$SaltAndParams 1152 pas = new DerValue[] { 1153 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1154 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1155 }; 1156 break; 1157 } 1158 } else { 1159 int[] epas = eTypes; 1160 if (options.containsKey(KDC.Option.RC4_FIRST_PREAUTH)) { 1161 for (int i=1; i<epas.length; i++) { 1162 if (epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC) { 1163 epas[i] = epas[0]; 1164 epas[0] = EncryptedData.ETYPE_ARCFOUR_HMAC; 1165 break; 1166 } 1167 }; 1168 } else if (options.containsKey(KDC.Option.ONLY_ONE_PREAUTH)) { 1169 epas = new int[] { eTypes[0] }; 1170 } 1171 pas2 = new DerValue[epas.length]; 1172 for (int i=0; i<epas.length; i++) { 1173 pas2[i] = new DerValue(new ETypeInfo2( 1174 epas[i], 1175 epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ? 1176 null : getSalt(body.cname), 1177 getParams(body.cname, epas[i])).asn1Encode()); 1178 } 1179 boolean allOld = true; 1180 for (int i: eTypes) { 1181 if (i >= EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96 && 1182 i != EncryptedData.ETYPE_ARCFOUR_HMAC) { 1183 allOld = false; 1184 break; 1185 } 1186 } 1187 if (allOld) { 1188 pas = new DerValue[epas.length]; 1189 for (int i=0; i<epas.length; i++) { 1190 pas[i] = new DerValue(new ETypeInfo( 1191 epas[i], 1192 epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ? 1193 null : getSalt(body.cname) 1194 ).asn1Encode()); 1195 } 1196 } 1197 } 1198 1199 DerOutputStream eid; 1200 if (pas2 != null) { 1201 eid = new DerOutputStream(); 1202 eid.putSequence(pas2); 1203 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO2, eid.toByteArray())); 1204 } 1205 if (pas != null) { 1206 eid = new DerOutputStream(); 1207 eid.putSequence(pas); 1208 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray())); 1209 } 1210 1211 PAData[] inPAs = KDCReqDotPAData(asReq); 1212 if (inPAs == null || inPAs.length == 0) { 1213 Object preauth = options.get(Option.PREAUTH_REQUIRED); 1214 if (preauth == null || preauth.equals(Boolean.TRUE)) { 1215 throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED); 1216 } 1217 } else { 1218 try { 1219 EncryptedData data = newEncryptedData( 1220 new DerValue(inPAs[0].getValue())); 1221 EncryptionKey pakey 1222 = keyForUser(body.cname, data.getEType(), false); 1223 data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS); 1224 } catch (Exception e) { 1225 KrbException ke = new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED); 1226 ke.initCause(e); 1227 throw ke; 1228 } 1229 bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true; 1230 } 1231 1232 TicketFlags tFlags = new TicketFlags(bFlags); 1233 EncTicketPart enc = new EncTicketPart( 1234 tFlags, 1235 key, 1236 body.cname, 1237 new TransitedEncoding(1, new byte[0]), 1238 timeAfter(0), 1239 from, 1240 till, rtime, 1241 body.addresses, 1242 null); 1243 Ticket t = new Ticket( 1244 service, 1245 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET) 1246 ); 1247 EncASRepPart enc_part = new EncASRepPart( 1248 key, 1249 new LastReq(new LastReqEntry[]{ 1250 new LastReqEntry(0, timeAfter(-10)) 1251 }), 1252 body.getNonce(), // TODO: detect replay? 1253 timeAfter(3600 * 24), 1254 // Next 5 and last MUST be same with ticket 1255 tFlags, 1256 timeAfter(0), 1257 from, 1258 till, rtime, 1259 service, 1260 body.addresses 1261 ); 1262 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), 1263 KeyUsage.KU_ENC_AS_REP_PART); 1264 ASRep asRep = new ASRep( 1265 outPAs.toArray(new PAData[outPAs.size()]), 1266 body.cname, 1267 t, 1268 edata); 1269 1270 System.out.println(" Return " + asRep.cname 1271 + " ticket for " + asRep.ticket.sname + ", flags " 1272 + tFlags); 1273 1274 DerOutputStream out = new DerOutputStream(); 1275 out.write(DerValue.createTag(DerValue.TAG_APPLICATION, 1276 true, (byte)Krb5.KRB_AS_REP), asRep.asn1Encode()); 1277 byte[] result = out.toByteArray(); 1278 1279 // Added feature: 1280 // Write the current issuing TGT into a ccache file specified 1281 // by the system property below. 1282 String ccache = System.getProperty("test.kdc.save.ccache"); 1283 if (ccache != null) { 1284 asRep.encKDCRepPart = enc_part; 1285 sun.security.krb5.internal.ccache.Credentials credentials = 1286 new sun.security.krb5.internal.ccache.Credentials(asRep); 1287 CredentialsCache cache = 1288 CredentialsCache.create(asReq.reqBody.cname, ccache); 1289 if (cache == null) { 1290 throw new IOException("Unable to create the cache file " + 1291 ccache); 1292 } 1293 cache.update(credentials); 1294 cache.save(); 1295 } 1296 1297 return result; 1298 } catch (KrbException ke) { 1299 ke.printStackTrace(System.out); 1300 KRBError kerr = ke.getError(); 1301 KDCReqBody body = asReq.reqBody; 1302 System.out.println(" Error " + ke.returnCode() 1303 + " " +ke.returnCodeMessage()); 1304 byte[] eData = null; 1305 if (kerr == null) { 1306 if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED || 1307 ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) { 1308 DerOutputStream bytes = new DerOutputStream(); 1309 bytes.write(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]).asn1Encode()); 1310 for (PAData p: outPAs) { 1311 bytes.write(p.asn1Encode()); 1312 } 1313 DerOutputStream temp = new DerOutputStream(); 1314 temp.write(DerValue.tag_Sequence, bytes); 1315 eData = temp.toByteArray(); 1316 } 1317 kerr = new KRBError(null, null, null, 1318 timeAfter(0), 1319 0, 1320 ke.returnCode(), 1321 body.cname, 1322 service, 1323 KrbException.errorMessage(ke.returnCode()), 1324 eData); 1325 } 1326 return kerr.asn1Encode(); 1327 } 1328 } 1329 filterSupported(int[] input)1330 private int[] filterSupported(int[] input) { 1331 int count = 0; 1332 for (int i = 0; i < input.length; i++) { 1333 if (!EType.isSupported(input[i])) { 1334 continue; 1335 } 1336 if (SUPPORTED_ETYPES != null) { 1337 boolean supported = false; 1338 for (String se : SUPPORTED_ETYPES.split(",")) { 1339 if (Config.getType(se) == input[i]) { 1340 supported = true; 1341 break; 1342 } 1343 } 1344 if (!supported) { 1345 continue; 1346 } 1347 } 1348 if (count != i) { 1349 input[count] = input[i]; 1350 } 1351 count++; 1352 } 1353 if (count != input.length) { 1354 input = Arrays.copyOf(input, count); 1355 } 1356 return input; 1357 } 1358 1359 /** 1360 * Generates a line for a KDC to put inside [realms] of krb5.conf 1361 * @return REALM.NAME = { kdc = host:port etc } 1362 */ realmLine()1363 private String realmLine() { 1364 StringBuilder sb = new StringBuilder(); 1365 sb.append(realm).append(" = {\n kdc = ") 1366 .append(kdc).append(':').append(port).append('\n'); 1367 for (String s: conf) { 1368 sb.append(" ").append(s).append('\n'); 1369 } 1370 return sb.append("}\n").toString(); 1371 } 1372 1373 /** 1374 * Start the KDC service. This server listens on both UDP and TCP using 1375 * the same port number. It uses three threads to deal with requests. 1376 * They can be set to daemon threads if requested. 1377 * @param port the port number to listen to. If zero, a random available 1378 * port no less than 8000 will be chosen and used. 1379 * @param asDaemon true if the KDC threads should be daemons 1380 * @throws java.io.IOException for any communication error 1381 */ startServer(int port, boolean asDaemon)1382 protected void startServer(int port, boolean asDaemon) throws IOException { 1383 if (nativeKdc != null) { 1384 startNativeServer(port, asDaemon); 1385 } else { 1386 startJavaServer(port, asDaemon); 1387 } 1388 } 1389 startNativeServer(int port, boolean asDaemon)1390 private void startNativeServer(int port, boolean asDaemon) throws IOException { 1391 nativeKdc.prepare(); 1392 nativeKdc.init(); 1393 kdcProc = nativeKdc.kdc(); 1394 } 1395 startJavaServer(int port, boolean asDaemon)1396 private void startJavaServer(int port, boolean asDaemon) throws IOException { 1397 if (port > 0) { 1398 u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1")); 1399 t1 = new ServerSocket(port); 1400 } else { 1401 while (true) { 1402 // Try to find a port number that's both TCP and UDP free 1403 try { 1404 port = 8000 + new java.util.Random().nextInt(10000); 1405 u1 = null; 1406 u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1")); 1407 t1 = new ServerSocket(port); 1408 break; 1409 } catch (Exception e) { 1410 if (u1 != null) u1.close(); 1411 } 1412 } 1413 } 1414 final DatagramSocket udp = u1; 1415 final ServerSocket tcp = t1; 1416 System.out.println("Start KDC on " + port); 1417 1418 this.port = port; 1419 1420 // The UDP consumer 1421 thread1 = new Thread() { 1422 public void run() { 1423 udpConsumerReady = true; 1424 while (true) { 1425 try { 1426 byte[] inbuf = new byte[8192]; 1427 DatagramPacket p = new DatagramPacket(inbuf, inbuf.length); 1428 udp.receive(p); 1429 System.out.println("-----------------------------------------------"); 1430 System.out.println(">>>>> UDP packet received"); 1431 q.put(new Job(processMessage(Arrays.copyOf(inbuf, p.getLength())), udp, p)); 1432 } catch (Exception e) { 1433 e.printStackTrace(); 1434 } 1435 } 1436 } 1437 }; 1438 thread1.setDaemon(asDaemon); 1439 thread1.start(); 1440 1441 // The TCP consumer 1442 thread2 = new Thread() { 1443 public void run() { 1444 tcpConsumerReady = true; 1445 while (true) { 1446 try { 1447 Socket socket = tcp.accept(); 1448 System.out.println("-----------------------------------------------"); 1449 System.out.println(">>>>> TCP connection established"); 1450 DataInputStream in = new DataInputStream(socket.getInputStream()); 1451 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); 1452 int len = in.readInt(); 1453 if (len > 65535) { 1454 throw new Exception("Huge request not supported"); 1455 } 1456 byte[] token = new byte[len]; 1457 in.readFully(token); 1458 q.put(new Job(processMessage(token), socket, out)); 1459 } catch (Exception e) { 1460 e.printStackTrace(); 1461 } 1462 } 1463 } 1464 }; 1465 thread2.setDaemon(asDaemon); 1466 thread2.start(); 1467 1468 // The dispatcher 1469 thread3 = new Thread() { 1470 public void run() { 1471 dispatcherReady = true; 1472 while (true) { 1473 try { 1474 q.take().send(); 1475 } catch (Exception e) { 1476 } 1477 } 1478 } 1479 }; 1480 thread3.setDaemon(true); 1481 thread3.start(); 1482 1483 // wait for the KDC is ready 1484 try { 1485 while (!isReady()) { 1486 Thread.sleep(100); 1487 } 1488 } catch(InterruptedException e) { 1489 throw new IOException(e); 1490 } 1491 } 1492 kinit(String user, String ccache)1493 public void kinit(String user, String ccache) throws Exception { 1494 if (user.indexOf('@') < 0) { 1495 user = user + "@" + realm; 1496 } 1497 if (nativeKdc != null) { 1498 nativeKdc.kinit(user, ccache); 1499 } else { 1500 Context.fromUserPass(user, passwords.get(user), false) 1501 .ccache(ccache); 1502 } 1503 } 1504 isReady()1505 boolean isReady() { 1506 return udpConsumerReady && tcpConsumerReady && dispatcherReady; 1507 } 1508 terminate()1509 public void terminate() { 1510 if (nativeKdc != null) { 1511 System.out.println("Killing kdc..."); 1512 kdcProc.destroyForcibly(); 1513 System.out.println("Done"); 1514 } else { 1515 try { 1516 thread1.stop(); 1517 thread2.stop(); 1518 thread3.stop(); 1519 u1.close(); 1520 t1.close(); 1521 } catch (Exception e) { 1522 // OK 1523 } 1524 } 1525 } 1526 startKDC(final String host, final String krbConfFileName, final String realm, final Map<String, String> principals, final String ktab, final KtabMode mode)1527 public static KDC startKDC(final String host, final String krbConfFileName, 1528 final String realm, final Map<String, String> principals, 1529 final String ktab, final KtabMode mode) { 1530 1531 KDC kdc; 1532 try { 1533 kdc = KDC.create(realm, host, 0, true); 1534 kdc.setOption(KDC.Option.PREAUTH_REQUIRED, Boolean.FALSE); 1535 if (krbConfFileName != null) { 1536 KDC.saveConfig(krbConfFileName, kdc); 1537 } 1538 1539 // Add principals 1540 if (principals != null) { 1541 principals.forEach((name, password) -> { 1542 if (password == null || password.isEmpty()) { 1543 System.out.println(String.format( 1544 "KDC:add a principal '%s' with a random " + 1545 "password", name)); 1546 kdc.addPrincipalRandKey(name); 1547 } else { 1548 System.out.println(String.format( 1549 "KDC:add a principal '%s' with '%s' password", 1550 name, password)); 1551 kdc.addPrincipal(name, password.toCharArray()); 1552 } 1553 }); 1554 } 1555 1556 // Create or append keys to existing keytab file 1557 if (ktab != null) { 1558 File ktabFile = new File(ktab); 1559 switch(mode) { 1560 case APPEND: 1561 if (ktabFile.exists()) { 1562 System.out.println(String.format( 1563 "KDC:append keys to an exising keytab " 1564 + "file %s", ktab)); 1565 kdc.appendKtab(ktab); 1566 } else { 1567 System.out.println(String.format( 1568 "KDC:create a new keytab file %s", ktab)); 1569 kdc.writeKtab(ktab); 1570 } 1571 break; 1572 case EXISTING: 1573 System.out.println(String.format( 1574 "KDC:use an existing keytab file %s", ktab)); 1575 break; 1576 default: 1577 throw new RuntimeException(String.format( 1578 "KDC:unsupported keytab mode: %s", mode)); 1579 } 1580 } 1581 1582 System.out.println(String.format( 1583 "KDC: started on %s:%s with '%s' realm", 1584 host, kdc.getPort(), realm)); 1585 } catch (Exception e) { 1586 throw new RuntimeException("KDC: unexpected exception", e); 1587 } 1588 1589 return kdc; 1590 } 1591 1592 /** 1593 * Helper class to encapsulate a job in a KDC. 1594 */ 1595 private static class Job { 1596 byte[] token; // The received request at creation time and 1597 // the response at send time 1598 Socket s; // The TCP socket from where the request comes 1599 DataOutputStream out; // The OutputStream of the TCP socket 1600 DatagramSocket s2; // The UDP socket from where the request comes 1601 DatagramPacket dp; // The incoming UDP datagram packet 1602 boolean useTCP; // Whether TCP or UDP is used 1603 1604 // Creates a job object for TCP Job(byte[] token, Socket s, DataOutputStream out)1605 Job(byte[] token, Socket s, DataOutputStream out) { 1606 useTCP = true; 1607 this.token = token; 1608 this.s = s; 1609 this.out = out; 1610 } 1611 1612 // Creates a job object for UDP Job(byte[] token, DatagramSocket s2, DatagramPacket dp)1613 Job(byte[] token, DatagramSocket s2, DatagramPacket dp) { 1614 useTCP = false; 1615 this.token = token; 1616 this.s2 = s2; 1617 this.dp = dp; 1618 } 1619 1620 // Sends the output back to the client send()1621 void send() { 1622 try { 1623 if (useTCP) { 1624 System.out.println(">>>>> TCP request honored"); 1625 out.writeInt(token.length); 1626 out.write(token); 1627 s.close(); 1628 } else { 1629 System.out.println(">>>>> UDP request honored"); 1630 s2.send(new DatagramPacket(token, token.length, dp.getAddress(), dp.getPort())); 1631 } 1632 } catch (Exception e) { 1633 e.printStackTrace(); 1634 } 1635 } 1636 } 1637 1638 /** 1639 * A native KDC using the binaries in nativePath. Attention: 1640 * this is using binaries, not an existing KDC instance. 1641 * An implementation of this takes care of configuration, 1642 * principal db managing and KDC startup. 1643 */ 1644 static abstract class NativeKdc { 1645 1646 protected Map<String,String> env; 1647 protected String nativePath; 1648 protected String base; 1649 protected String realm; 1650 protected int port; 1651 NativeKdc(String nativePath, KDC kdc)1652 NativeKdc(String nativePath, KDC kdc) { 1653 if (kdc.port == 0) { 1654 kdc.port = 8000 + new java.util.Random().nextInt(10000); 1655 } 1656 this.nativePath = nativePath; 1657 this.realm = kdc.realm; 1658 this.port = kdc.port; 1659 this.base = Paths.get("" + port).toAbsolutePath().toString(); 1660 } 1661 1662 // Add a new principal addPrincipal(String user, String pass)1663 abstract void addPrincipal(String user, String pass); 1664 // Add a keytab entry ktadd(String user, String ktab)1665 abstract void ktadd(String user, String ktab); 1666 // Initialize KDC init()1667 abstract void init(); 1668 // Start kdc kdc()1669 abstract Process kdc(); 1670 // Configuration prepare()1671 abstract void prepare(); 1672 // Fill ccache kinit(String user, String ccache)1673 abstract void kinit(String user, String ccache); 1674 get(KDC kdc)1675 static NativeKdc get(KDC kdc) { 1676 String prop = System.getProperty("native.kdc.path"); 1677 if (prop == null) { 1678 return null; 1679 } else if (Files.exists(Paths.get(prop, "sbin/krb5kdc"))) { 1680 return new MIT(true, prop, kdc); 1681 } else if (Files.exists(Paths.get(prop, "kdc/krb5kdc"))) { 1682 return new MIT(false, prop, kdc); 1683 } else if (Files.exists(Paths.get(prop, "libexec/kdc"))) { 1684 return new Heimdal(prop, kdc); 1685 } else { 1686 throw new IllegalArgumentException("Strange " + prop); 1687 } 1688 } 1689 run(boolean wait, String... cmd)1690 Process run(boolean wait, String... cmd) { 1691 try { 1692 System.out.println("Running " + cmd2str(env, cmd)); 1693 ProcessBuilder pb = new ProcessBuilder(); 1694 pb.inheritIO(); 1695 pb.environment().putAll(env); 1696 Process p = pb.command(cmd).start(); 1697 if (wait) { 1698 if (p.waitFor() < 0) { 1699 throw new RuntimeException("exit code is not null"); 1700 } 1701 return null; 1702 } else { 1703 return p; 1704 } 1705 } catch (Exception e) { 1706 throw new RuntimeException(e); 1707 } 1708 } 1709 cmd2str(Map<String,String> env, String... cmd)1710 private String cmd2str(Map<String,String> env, String... cmd) { 1711 return env.entrySet().stream().map(e -> e.getKey()+"="+e.getValue()) 1712 .collect(Collectors.joining(" ")) + " " + 1713 Stream.of(cmd).collect(Collectors.joining(" ")); 1714 } 1715 } 1716 1717 // Heimdal KDC. Build your own and run "make install" to nativePath. 1718 static class Heimdal extends NativeKdc { 1719 Heimdal(String nativePath, KDC kdc)1720 Heimdal(String nativePath, KDC kdc) { 1721 super(nativePath, kdc); 1722 this.env = Map.of( 1723 "KRB5_CONFIG", base + "/krb5.conf", 1724 "KRB5_TRACE", "/dev/stderr", 1725 "DYLD_LIBRARY_PATH", nativePath + "/lib", 1726 "LD_LIBRARY_PATH", nativePath + "/lib"); 1727 } 1728 1729 @Override addPrincipal(String user, String pass)1730 public void addPrincipal(String user, String pass) { 1731 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1732 "add", "-p", pass, "--use-defaults", user); 1733 } 1734 1735 @Override ktadd(String user, String ktab)1736 public void ktadd(String user, String ktab) { 1737 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1738 "ext_keytab", "-k", ktab, user); 1739 } 1740 1741 @Override init()1742 public void init() { 1743 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1744 "init", "--realm-max-ticket-life=1day", 1745 "--realm-max-renewable-life=1month", realm); 1746 } 1747 1748 @Override kdc()1749 public Process kdc() { 1750 return run(false, nativePath + "/libexec/kdc", 1751 "--addresses=127.0.0.1", "-P", "" + port); 1752 } 1753 1754 @Override prepare()1755 public void prepare() { 1756 try { 1757 Files.createDirectory(Paths.get(base)); 1758 Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList( 1759 "[libdefaults]", 1760 "default_realm = " + realm, 1761 "default_keytab_name = FILE:" + base + "/krb5.keytab", 1762 "forwardable = true", 1763 "dns_lookup_kdc = no", 1764 "dns_lookup_realm = no", 1765 "dns_canonicalize_hostname = false", 1766 "\n[realms]", 1767 realm + " = {", 1768 " kdc = localhost:" + port, 1769 "}", 1770 "\n[kdc]", 1771 "db-dir = " + base, 1772 "database = {", 1773 " label = {", 1774 " dbname = " + base + "/current-db", 1775 " realm = " + realm, 1776 " mkey_file = " + base + "/mkey.file", 1777 " acl_file = " + base + "/heimdal.acl", 1778 " log_file = " + base + "/current.log", 1779 " }", 1780 "}", 1781 SUPPORTED_ETYPES == null ? "" 1782 : ("\n[kadmin]\ndefault_keys = " 1783 + (SUPPORTED_ETYPES + ",") 1784 .replaceAll(",", ":pw-salt ")), 1785 "\n[logging]", 1786 "kdc = 0-/FILE:" + base + "/messages.log", 1787 "krb5 = 0-/FILE:" + base + "/messages.log", 1788 "default = 0-/FILE:" + base + "/messages.log" 1789 )); 1790 } catch (IOException e) { 1791 throw new UncheckedIOException(e); 1792 } 1793 } 1794 1795 @Override kinit(String user, String ccache)1796 void kinit(String user, String ccache) { 1797 String tmpName = base + "/" + user + "." + 1798 System.identityHashCode(this) + ".keytab"; 1799 ktadd(user, tmpName); 1800 run(true, nativePath + "/bin/kinit", 1801 "-f", "-t", tmpName, "-c", ccache, user); 1802 } 1803 } 1804 1805 // MIT krb5 KDC. Make your own exploded (install == false), or 1806 // "make install" into nativePath (install == true). 1807 static class MIT extends NativeKdc { 1808 1809 private boolean install; // "make install" or "make" 1810 MIT(boolean install, String nativePath, KDC kdc)1811 MIT(boolean install, String nativePath, KDC kdc) { 1812 super(nativePath, kdc); 1813 this.install = install; 1814 this.env = Map.of( 1815 "KRB5_KDC_PROFILE", base + "/kdc.conf", 1816 "KRB5_CONFIG", base + "/krb5.conf", 1817 "KRB5_TRACE", "/dev/stderr", 1818 "DYLD_LIBRARY_PATH", nativePath + "/lib", 1819 "LD_LIBRARY_PATH", nativePath + "/lib"); 1820 } 1821 1822 @Override addPrincipal(String user, String pass)1823 public void addPrincipal(String user, String pass) { 1824 run(true, nativePath + 1825 (install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local", 1826 "-q", "addprinc -pw " + pass + " " + user); 1827 } 1828 1829 @Override ktadd(String user, String ktab)1830 public void ktadd(String user, String ktab) { 1831 run(true, nativePath + 1832 (install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local", 1833 "-q", "ktadd -k " + ktab + " -norandkey " + user); 1834 } 1835 1836 @Override init()1837 public void init() { 1838 run(true, nativePath + 1839 (install ? "/sbin/" : "/kadmin/dbutil/") + "kdb5_util", 1840 "create", "-s", "-W", "-P", "olala"); 1841 } 1842 1843 @Override kdc()1844 public Process kdc() { 1845 return run(false, nativePath + 1846 (install ? "/sbin/" : "/kdc/") + "krb5kdc", 1847 "-n"); 1848 } 1849 1850 @Override prepare()1851 public void prepare() { 1852 try { 1853 Files.createDirectory(Paths.get(base)); 1854 Files.write(Paths.get(base + "/kdc.conf"), Arrays.asList( 1855 "[kdcdefaults]", 1856 "\n[realms]", 1857 realm + "= {", 1858 " kdc_listen = " + this.port, 1859 " kdc_tcp_listen = " + this.port, 1860 " database_name = " + base + "/principal", 1861 " key_stash_file = " + base + "/.k5.ATHENA.MIT.EDU", 1862 SUPPORTED_ETYPES == null ? "" 1863 : (" supported_enctypes = " 1864 + (SUPPORTED_ETYPES + ",") 1865 .replaceAll(",", ":normal ")), 1866 "}" 1867 )); 1868 Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList( 1869 "[libdefaults]", 1870 "default_realm = " + realm, 1871 "default_keytab_name = FILE:" + base + "/krb5.keytab", 1872 "forwardable = true", 1873 "dns_lookup_kdc = no", 1874 "dns_lookup_realm = no", 1875 "dns_canonicalize_hostname = false", 1876 "\n[realms]", 1877 realm + " = {", 1878 " kdc = localhost:" + port, 1879 "}", 1880 "\n[logging]", 1881 "kdc = FILE:" + base + "/krb5kdc.log" 1882 )); 1883 } catch (IOException e) { 1884 throw new UncheckedIOException(e); 1885 } 1886 } 1887 1888 @Override kinit(String user, String ccache)1889 void kinit(String user, String ccache) { 1890 String tmpName = base + "/" + user + "." + 1891 System.identityHashCode(this) + ".keytab"; 1892 ktadd(user, tmpName); 1893 run(true, nativePath + 1894 (install ? "/bin/" : "/clients/kinit/") + "kinit", 1895 "-f", "-t", tmpName, "-c", ccache, user); 1896 } 1897 } 1898 1899 // Calling private methods thru reflections 1900 private static final Field getPADataField; 1901 private static final Field getEType; 1902 private static final Constructor<EncryptedData> ctorEncryptedData; 1903 private static final Method stringToKey; 1904 private static final Field getAddlTkt; 1905 1906 static { 1907 try { 1908 ctorEncryptedData = EncryptedData.class.getDeclaredConstructor(DerValue.class); 1909 ctorEncryptedData.setAccessible(true); 1910 getPADataField = KDCReq.class.getDeclaredField("pAData"); 1911 getPADataField.setAccessible(true); 1912 getEType = KDCReqBody.class.getDeclaredField("eType"); 1913 getEType.setAccessible(true); 1914 stringToKey = EncryptionKey.class.getDeclaredMethod( 1915 "stringToKey", 1916 char[].class, String.class, byte[].class, Integer.TYPE); 1917 stringToKey.setAccessible(true); 1918 getAddlTkt = KDCReqBody.class.getDeclaredField("additionalTickets"); 1919 getAddlTkt.setAccessible(true); 1920 } catch (NoSuchFieldException nsfe) { 1921 throw new AssertionError(nsfe); 1922 } catch (NoSuchMethodException nsme) { 1923 throw new AssertionError(nsme); 1924 } 1925 } newEncryptedData(DerValue der)1926 private EncryptedData newEncryptedData(DerValue der) { 1927 try { 1928 return ctorEncryptedData.newInstance(der); 1929 } catch (Exception e) { 1930 throw new AssertionError(e); 1931 } 1932 } KDCReqDotPAData(KDCReq req)1933 private static PAData[] KDCReqDotPAData(KDCReq req) { 1934 try { 1935 return (PAData[])getPADataField.get(req); 1936 } catch (Exception e) { 1937 throw new AssertionError(e); 1938 } 1939 } KDCReqBodyDotEType(KDCReqBody body)1940 private static int[] KDCReqBodyDotEType(KDCReqBody body) { 1941 try { 1942 return (int[]) getEType.get(body); 1943 } catch (Exception e) { 1944 throw new AssertionError(e); 1945 } 1946 } EncryptionKeyDotStringToKey(char[] password, String salt, byte[] s2kparams, int keyType)1947 private static byte[] EncryptionKeyDotStringToKey(char[] password, String salt, 1948 byte[] s2kparams, int keyType) throws KrbCryptoException { 1949 try { 1950 return (byte[])stringToKey.invoke( 1951 null, password, salt, s2kparams, keyType); 1952 } catch (InvocationTargetException ex) { 1953 throw (KrbCryptoException)ex.getCause(); 1954 } catch (Exception e) { 1955 throw new AssertionError(e); 1956 } 1957 } KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body)1958 private static Ticket KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body) { 1959 try { 1960 return ((Ticket[])getAddlTkt.get(body))[0]; 1961 } catch (Exception e) { 1962 throw new AssertionError(e); 1963 } 1964 } 1965 } 1966