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