1/* 2 * Copyright (c) 2008, 2017, 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 24import java.lang.reflect.Constructor; 25import java.lang.reflect.Field; 26import java.lang.reflect.InvocationTargetException; 27import java.net.*; 28import java.io.*; 29import java.lang.reflect.Method; 30import java.security.SecureRandom; 31import java.time.Instant; 32import java.time.temporal.ChronoUnit; 33import java.util.*; 34import java.util.concurrent.*; 35 36import sun.security.krb5.*; 37import sun.security.krb5.internal.*; 38import sun.security.krb5.internal.ccache.CredentialsCache; 39import sun.security.krb5.internal.crypto.EType; 40import sun.security.krb5.internal.crypto.KeyUsage; 41import sun.security.krb5.internal.ktab.KeyTab; 42import sun.security.util.DerInputStream; 43import sun.security.util.DerOutputStream; 44import sun.security.util.DerValue; 45 46/** 47 * A KDC server. 48 * <p> 49 * Features: 50 * <ol> 51 * <li> Supports TCP and UDP 52 * <li> Supports AS-REQ and TGS-REQ 53 * <li> Principal db and other settings hard coded in application 54 * <li> Options, say, request preauth or not 55 * </ol> 56 * Side effects: 57 * <ol> 58 * <li> The Sun-internal class <code>sun.security.krb5.Config</code> is a 59 * singleton and initialized according to Kerberos settings (krb5.conf and 60 * java.security.krb5.* system properties). This means once it's initialized 61 * it will not automatically notice any changes to these settings (or file 62 * changes of krb5.conf). The KDC class normally does not touch these 63 * settings (except for the <code>writeKtab()</code> method). However, to make 64 * sure nothing ever goes wrong, if you want to make any changes to these 65 * settings after calling a KDC method, call <code>Config.refresh()</code> to 66 * make sure your changes are reflected in the <code>Config</code> object. 67 * </ol> 68 * System properties recognized: 69 * <ul> 70 * <li>test.kdc.save.ccache 71 * </ul> 72 * Issues and TODOs: 73 * <ol> 74 * <li> Generates krb5.conf to be used on another machine, currently the kdc is 75 * always localhost 76 * <li> More options to KDC, say, error output, say, response nonce != 77 * request nonce 78 * </ol> 79 * Note: This program uses internal krb5 classes (including reflection to 80 * access private fields and methods). 81 * <p> 82 * Usages: 83 * <p> 84 * 1. Init and start the KDC: 85 * <pre> 86 * KDC kdc = KDC.create("REALM.NAME", port, isDaemon); 87 * KDC kdc = KDC.create("REALM.NAME"); 88 * </pre> 89 * Here, <code>port</code> is the UDP and TCP port number the KDC server 90 * listens on. If zero, a random port is chosen, which you can use getPort() 91 * later to retrieve the value. 92 * <p> 93 * If <code>isDaemon</code> is true, the KDC worker threads will be daemons. 94 * <p> 95 * The shortcut <code>KDC.create("REALM.NAME")</code> has port=0 and 96 * isDaemon=false, and is commonly used in an embedded KDC. 97 * <p> 98 * 2. Adding users: 99 * <pre> 100 * kdc.addPrincipal(String principal_name, char[] password); 101 * kdc.addPrincipalRandKey(String principal_name); 102 * </pre> 103 * A service principal's name should look like "host/f.q.d.n". The second form 104 * generates a random key. To expose this key, call <code>writeKtab()</code> to 105 * save the keys into a keytab file. 106 * <p> 107 * Note that you need to add the principal name krbtgt/REALM.NAME yourself. 108 * <p> 109 * Note that you can safely add a principal at any time after the KDC is 110 * started and before a user requests info on this principal. 111 * <p> 112 * 3. Other public methods: 113 * <ul> 114 * <li> <code>getPort</code>: Returns the port number the KDC uses 115 * <li> <code>getRealm</code>: Returns the realm name 116 * <li> <code>writeKtab</code>: Writes all principals' keys into a keytab file 117 * <li> <code>saveConfig</code>: Saves a krb5.conf file to access this KDC 118 * <li> <code>setOption</code>: Sets various options 119 * </ul> 120 * Read the javadoc for details. Lazy developer can use <code>OneKDC</code> 121 * directly. 122 */ 123public class KDC { 124 125 public static final int DEFAULT_LIFETIME = 39600; 126 public static final int DEFAULT_RENEWTIME = 86400; 127 128 public static String NOT_EXISTING_HOST = "not.existing.host"; 129 130 // Under the hood. 131 132 // The random generator to generate random keys (including session keys) 133 private static SecureRandom secureRandom = new SecureRandom(); 134 135 // Principal db. principal -> pass. A case-insensitive TreeMap is used 136 // so that even if the client provides a name with different case, the KDC 137 // can still locate the principal and give back correct salt. 138 private TreeMap<String,char[]> passwords = new TreeMap<> 139 (String.CASE_INSENSITIVE_ORDER); 140 141 // Realm name 142 private String realm; 143 // KDC 144 private String kdc; 145 // Service port number 146 private int port; 147 // The request/response job queue 148 private BlockingQueue<Job> q = new ArrayBlockingQueue<>(100); 149 // Options 150 private Map<Option,Object> options = new HashMap<>(); 151 // Realm-specific krb5.conf settings 152 private List<String> conf = new ArrayList<>(); 153 154 private Thread thread1, thread2, thread3; 155 private volatile boolean udpConsumerReady = false; 156 private volatile boolean tcpConsumerReady = false; 157 private volatile boolean dispatcherReady = false; 158 DatagramSocket u1 = null; 159 ServerSocket t1 = null; 160 161 public static enum KtabMode { APPEND, EXISTING }; 162 163 /** 164 * Option names, to be expanded forever. 165 */ 166 public static enum Option { 167 /** 168 * Whether pre-authentication is required. Default Boolean.TRUE 169 */ 170 PREAUTH_REQUIRED, 171 /** 172 * Only issue TGT in RC4 173 */ 174 ONLY_RC4_TGT, 175 /** 176 * Use RC4 as the first in preauth 177 */ 178 RC4_FIRST_PREAUTH, 179 /** 180 * Use only one preauth, so that some keys are not easy to generate 181 */ 182 ONLY_ONE_PREAUTH, 183 /** 184 * Set all name-type to a value in response 185 */ 186 RESP_NT, 187 /** 188 * Multiple ETYPE-INFO-ENTRY with same etype but different salt 189 */ 190 DUP_ETYPE, 191 /** 192 * What backend server can be delegated to 193 */ 194 OK_AS_DELEGATE, 195 /** 196 * Allow S4U2self, List<String> of middle servers. 197 * If not set, means KDC does not understand S4U2self at all, therefore 198 * would ignore any PA-FOR-USER request and send a ticket using the 199 * cname of teh requestor. If set, it returns FORWARDABLE tickets to 200 * a server with its name in the list 201 */ 202 ALLOW_S4U2SELF, 203 /** 204 * Allow S4U2proxy, Map<String,List<String>> of middle servers to 205 * backends. If not set or a backend not in a server's list, 206 * Krb5.KDC_ERR_POLICY will be send for S4U2proxy request. 207 */ 208 ALLOW_S4U2PROXY, 209 /** 210 * Sensitive accounts can never be delegated. 211 */ 212 SENSITIVE_ACCOUNTS, 213 /** 214 * If true, will check if TGS-REQ contains a non-null addresses field. 215 */ 216 CHECK_ADDRESSES, 217 }; 218 219 static { 220 if (System.getProperty("jdk.net.hosts.file") == null) { 221 String hostsFileName = System.getProperty("test.src", ".") + "/TestHosts"; 222 System.setProperty("jdk.net.hosts.file", hostsFileName); 223 } 224 } 225 226 /** 227 * A standalone KDC server. 228 */ 229 public static void main(String[] args) throws Exception { 230 int port = args.length > 0 ? Integer.parseInt(args[0]) : 0; 231 KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", port, false); 232 kdc.addPrincipal("dummy", "bogus".toCharArray()); 233 kdc.addPrincipal("foo", "bar".toCharArray()); 234 kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE"); 235 kdc.addPrincipalRandKey("server/host.rabbit.hole"); 236 kdc.addPrincipalRandKey("backend/host.rabbit.hole"); 237 KDC.saveConfig("krb5.conf", kdc, "forwardable = true"); 238 } 239 240 /** 241 * Creates and starts a KDC running as a daemon on a random port. 242 * @param realm the realm name 243 * @return the running KDC instance 244 * @throws java.io.IOException for any socket creation error 245 */ 246 public static KDC create(String realm) throws IOException { 247 return create(realm, "kdc." + realm.toLowerCase(Locale.US), 0, true); 248 } 249 250 public static KDC existing(String realm, String kdc, int port) { 251 KDC k = new KDC(realm, kdc); 252 k.port = port; 253 return k; 254 } 255 256 /** 257 * Creates and starts a KDC server. 258 * @param realm the realm name 259 * @param port the TCP and UDP port to listen to. A random port will to 260 * chosen if zero. 261 * @param asDaemon if true, KDC threads will be daemons. Otherwise, not. 262 * @return the running KDC instance 263 * @throws java.io.IOException for any socket creation error 264 */ 265 public static KDC create(String realm, String kdc, int port, boolean asDaemon) throws IOException { 266 return new KDC(realm, kdc, port, asDaemon); 267 } 268 269 /** 270 * Sets an option 271 * @param key the option name 272 * @param value the value 273 */ 274 public void setOption(Option key, Object value) { 275 if (value == null) { 276 options.remove(key); 277 } else { 278 options.put(key, value); 279 } 280 } 281 282 /** 283 * Writes or appends keys into a keytab. 284 * <p> 285 * Attention: This is the most basic one of a series of methods below on 286 * keytab creation or modification. All these methods reference krb5.conf 287 * settings. If you need to modify krb5.conf or switch to another krb5.conf 288 * later, please call <code>Config.refresh()</code> again. For example: 289 * <pre> 290 * kdc.writeKtab("/etc/kdc/ktab", true); // Config is initialized, 291 * System.setProperty("java.security.krb5.conf", "/home/mykrb5.conf"); 292 * Config.refresh(); 293 * </pre> 294 * Inside this method there are 2 places krb5.conf is used: 295 * <ol> 296 * <li> (Fatal) Generating keys: EncryptionKey.acquireSecretKeys 297 * <li> (Has workaround) Creating PrincipalName 298 * </ol> 299 * @param tab the keytab file name 300 * @param append true if append, otherwise, overwrite. 301 * @param names the names to write into, write all if names is empty 302 */ 303 public void writeKtab(String tab, boolean append, String... names) 304 throws IOException, KrbException { 305 KeyTab ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab); 306 Iterable<String> entries = 307 (names.length != 0) ? Arrays.asList(names): passwords.keySet(); 308 for (String name : entries) { 309 char[] pass = passwords.get(name); 310 int kvno = 0; 311 if (Character.isDigit(pass[pass.length-1])) { 312 kvno = pass[pass.length-1] - '0'; 313 } 314 PrincipalName pn = new PrincipalName(name, 315 name.indexOf('/') < 0 ? 316 PrincipalName.KRB_NT_UNKNOWN : 317 PrincipalName.KRB_NT_SRV_HST); 318 ktab.addEntry(pn, 319 getSalt(pn), 320 pass, 321 kvno, 322 true); 323 } 324 ktab.save(); 325 } 326 327 /** 328 * Writes all principals' keys from multiple KDCs into one keytab file. 329 * @throws java.io.IOException for any file output error 330 * @throws sun.security.krb5.KrbException for any realm and/or principal 331 * name error. 332 */ 333 public static void writeMultiKtab(String tab, KDC... kdcs) 334 throws IOException, KrbException { 335 KeyTab.create(tab).save(); // Empty the old keytab 336 appendMultiKtab(tab, kdcs); 337 } 338 339 /** 340 * Appends all principals' keys from multiple KDCs to one keytab file. 341 */ 342 public static void appendMultiKtab(String tab, KDC... kdcs) 343 throws IOException, KrbException { 344 for (KDC kdc: kdcs) { 345 kdc.writeKtab(tab, true); 346 } 347 } 348 349 /** 350 * Write a ktab for this KDC. 351 */ 352 public void writeKtab(String tab) throws IOException, KrbException { 353 writeKtab(tab, false); 354 } 355 356 /** 357 * Appends keys in this KDC to a ktab. 358 */ 359 public void appendKtab(String tab) throws IOException, KrbException { 360 writeKtab(tab, true); 361 } 362 363 /** 364 * Adds a new principal to this realm with a given password. 365 * @param user the principal's name. For a service principal, use the 366 * form of host/f.q.d.n 367 * @param pass the password for the principal 368 */ 369 public void addPrincipal(String user, char[] pass) { 370 if (user.indexOf('@') < 0) { 371 user = user + "@" + realm; 372 } 373 passwords.put(user, pass); 374 } 375 376 /** 377 * Adds a new principal to this realm with a random password 378 * @param user the principal's name. For a service principal, use the 379 * form of host/f.q.d.n 380 */ 381 public void addPrincipalRandKey(String user) { 382 addPrincipal(user, randomPassword()); 383 } 384 385 /** 386 * Returns the name of this realm 387 * @return the name of this realm 388 */ 389 public String getRealm() { 390 return realm; 391 } 392 393 /** 394 * Returns the name of kdc 395 * @return the name of kdc 396 */ 397 public String getKDC() { 398 return kdc; 399 } 400 401 /** 402 * Add realm-specific krb5.conf setting 403 */ 404 public void addConf(String s) { 405 conf.add(s); 406 } 407 408 /** 409 * Writes a krb5.conf for one or more KDC that includes KDC locations for 410 * each realm and the default realm name. You can also add extra strings 411 * into the file. The method should be called like: 412 * <pre> 413 * KDC.saveConfig("krb5.conf", kdc1, kdc2, ..., line1, line2, ...); 414 * </pre> 415 * Here you can provide one or more kdc# and zero or more line# arguments. 416 * The line# will be put after [libdefaults] and before [realms]. Therefore 417 * you can append new lines into [libdefaults] and/or create your new 418 * stanzas as well. Note that a newline character will be appended to 419 * each line# argument. 420 * <p> 421 * For example: 422 * <pre> 423 * KDC.saveConfig("krb5.conf", this); 424 * </pre> 425 * generates: 426 * <pre> 427 * [libdefaults] 428 * default_realm = REALM.NAME 429 * 430 * [realms] 431 * REALM.NAME = { 432 * kdc = host:port_number 433 * # realm-specific settings 434 * } 435 * </pre> 436 * 437 * Another example: 438 * <pre> 439 * KDC.saveConfig("krb5.conf", kdc1, kdc2, "forwardable = true", "", 440 * "[domain_realm]", 441 * ".kdc1.com = KDC1.NAME"); 442 * </pre> 443 * generates: 444 * <pre> 445 * [libdefaults] 446 * default_realm = KDC1.NAME 447 * forwardable = true 448 * 449 * [domain_realm] 450 * .kdc1.com = KDC1.NAME 451 * 452 * [realms] 453 * KDC1.NAME = { 454 * kdc = host:port1 455 * } 456 * KDC2.NAME = { 457 * kdc = host:port2 458 * } 459 * </pre> 460 * @param file the name of the file to write into 461 * @param kdc the first (and default) KDC 462 * @param more more KDCs or extra lines (in their appearing order) to 463 * insert into the krb5.conf file. This method reads each argument's type 464 * to determine what it's for. This argument can be empty. 465 * @throws java.io.IOException for any file output error 466 */ 467 public static void saveConfig(String file, KDC kdc, Object... more) 468 throws IOException { 469 File f = new File(file); 470 StringBuffer sb = new StringBuffer(); 471 sb.append("[libdefaults]\ndefault_realm = "); 472 sb.append(kdc.realm); 473 sb.append("\n"); 474 for (Object o: more) { 475 if (o instanceof String) { 476 sb.append(o); 477 sb.append("\n"); 478 } 479 } 480 sb.append("\n[realms]\n"); 481 sb.append(kdc.realmLine()); 482 for (Object o: more) { 483 if (o instanceof KDC) { 484 sb.append(((KDC)o).realmLine()); 485 } 486 } 487 FileOutputStream fos = new FileOutputStream(f); 488 fos.write(sb.toString().getBytes()); 489 fos.close(); 490 } 491 492 /** 493 * Returns the service port of the KDC server. 494 * @return the KDC service port 495 */ 496 public int getPort() { 497 return port; 498 } 499 500 // Private helper methods 501 502 /** 503 * Private constructor, cannot be called outside. 504 * @param realm 505 */ 506 private KDC(String realm, String kdc) { 507 this.realm = realm; 508 this.kdc = kdc; 509 } 510 511 /** 512 * A constructor that starts the KDC service also. 513 */ 514 protected KDC(String realm, String kdc, int port, boolean asDaemon) 515 throws IOException { 516 this(realm, kdc); 517 startServer(port, asDaemon); 518 } 519 /** 520 * Generates a 32-char random password 521 * @return the password 522 */ 523 private static char[] randomPassword() { 524 char[] pass = new char[32]; 525 for (int i=0; i<31; i++) 526 pass[i] = (char)secureRandom.nextInt(); 527 // The last char cannot be a number, otherwise, keyForUser() 528 // believes it's a sign of kvno 529 pass[31] = 'Z'; 530 return pass; 531 } 532 533 /** 534 * Generates a random key for the given encryption type. 535 * @param eType the encryption type 536 * @return the generated key 537 * @throws sun.security.krb5.KrbException for unknown/unsupported etype 538 */ 539 private static EncryptionKey generateRandomKey(int eType) 540 throws KrbException { 541 // Is 32 enough for AES256? I should have generated the keys directly 542 // but different cryptos have different rules on what keys are valid. 543 char[] pass = randomPassword(); 544 String algo; 545 switch (eType) { 546 case EncryptedData.ETYPE_DES_CBC_MD5: algo = "DES"; break; 547 case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: algo = "DESede"; break; 548 case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: algo = "AES128"; break; 549 case EncryptedData.ETYPE_ARCFOUR_HMAC: algo = "ArcFourHMAC"; break; 550 case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: algo = "AES256"; break; 551 default: algo = "DES"; break; 552 } 553 return new EncryptionKey(pass, "NOTHING", algo); // Silly 554 } 555 556 /** 557 * Returns the password for a given principal 558 * @param p principal 559 * @return the password 560 * @throws sun.security.krb5.KrbException when the principal is not inside 561 * the database. 562 */ 563 private char[] getPassword(PrincipalName p, boolean server) 564 throws KrbException { 565 String pn = p.toString(); 566 if (p.getRealmString() == null) { 567 pn = pn + "@" + getRealm(); 568 } 569 char[] pass = passwords.get(pn); 570 if (pass == null) { 571 throw new KrbException(server? 572 Krb5.KDC_ERR_S_PRINCIPAL_UNKNOWN: 573 Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN, pn.toString()); 574 } 575 return pass; 576 } 577 578 /** 579 * Returns the salt string for the principal. 580 * @param p principal 581 * @return the salt 582 */ 583 protected String getSalt(PrincipalName p) { 584 String pn = p.toString(); 585 if (p.getRealmString() == null) { 586 pn = pn + "@" + getRealm(); 587 } 588 if (passwords.containsKey(pn)) { 589 try { 590 // Find the principal name with correct case. 591 p = new PrincipalName(passwords.ceilingEntry(pn).getKey()); 592 } catch (RealmException re) { 593 // Won't happen 594 } 595 } 596 String s = p.getRealmString(); 597 if (s == null) s = getRealm(); 598 for (String n: p.getNameStrings()) { 599 s += n; 600 } 601 return s; 602 } 603 604 /** 605 * Returns the key for a given principal of the given encryption type 606 * @param p the principal 607 * @param etype the encryption type 608 * @param server looking for a server principal? 609 * @return the key 610 * @throws sun.security.krb5.KrbException for unknown/unsupported etype 611 */ 612 private EncryptionKey keyForUser(PrincipalName p, int etype, boolean server) 613 throws KrbException { 614 try { 615 // Do not call EncryptionKey.acquireSecretKeys(), otherwise 616 // the krb5.conf config file would be loaded. 617 Integer kvno = null; 618 // For service whose password ending with a number, use it as kvno. 619 // Kvno must be postive. 620 if (p.toString().indexOf('/') > 0) { 621 char[] pass = getPassword(p, server); 622 if (Character.isDigit(pass[pass.length-1])) { 623 kvno = pass[pass.length-1] - '0'; 624 } 625 } 626 return new EncryptionKey(EncryptionKeyDotStringToKey( 627 getPassword(p, server), getSalt(p), null, etype), 628 etype, kvno); 629 } catch (KrbException ke) { 630 throw ke; 631 } catch (Exception e) { 632 throw new RuntimeException(e); // should not happen 633 } 634 } 635 636 /** 637 * Processes an incoming request and generates a response. 638 * @param in the request 639 * @return the response 640 * @throws java.lang.Exception for various errors 641 */ 642 protected byte[] processMessage(byte[] in) throws Exception { 643 if ((in[0] & 0x1f) == Krb5.KRB_AS_REQ) 644 return processAsReq(in); 645 else 646 return processTgsReq(in); 647 } 648 649 /** 650 * Processes a TGS_REQ and generates a TGS_REP (or KRB_ERROR) 651 * @param in the request 652 * @return the response 653 * @throws java.lang.Exception for various errors 654 */ 655 protected byte[] processTgsReq(byte[] in) throws Exception { 656 TGSReq tgsReq = new TGSReq(in); 657 PrincipalName service = tgsReq.reqBody.sname; 658 if (options.containsKey(KDC.Option.RESP_NT)) { 659 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT), 660 service.getNameStrings(), service.getRealm()); 661 } 662 try { 663 System.out.println(realm + "> " + tgsReq.reqBody.cname + 664 " sends TGS-REQ for " + 665 service + ", " + tgsReq.reqBody.kdcOptions); 666 KDCReqBody body = tgsReq.reqBody; 667 int[] eTypes = KDCReqBodyDotEType(body); 668 int e2 = eTypes[0]; // etype for outgoing session key 669 int e3 = eTypes[0]; // etype for outgoing ticket 670 671 PAData[] pas = KDCReqDotPAData(tgsReq); 672 673 Ticket tkt = null; 674 EncTicketPart etp = null; 675 676 PrincipalName cname = null; 677 boolean allowForwardable = true; 678 679 if (pas == null || pas.length == 0) { 680 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 681 } else { 682 PrincipalName forUserCName = null; 683 for (PAData pa: pas) { 684 if (pa.getType() == Krb5.PA_TGS_REQ) { 685 APReq apReq = new APReq(pa.getValue()); 686 EncryptedData ed = apReq.authenticator; 687 tkt = apReq.ticket; 688 int te = tkt.encPart.getEType(); 689 EncryptionKey kkey = keyForUser(tkt.sname, te, true); 690 byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET); 691 DerInputStream derIn = new DerInputStream(bb); 692 DerValue der = derIn.getDerValue(); 693 etp = new EncTicketPart(der.toByteArray()); 694 // Finally, cname will be overwritten by PA-FOR-USER 695 // if it exists. 696 cname = etp.cname; 697 System.out.println(realm + "> presenting a ticket of " 698 + etp.cname + " to " + tkt.sname); 699 } else if (pa.getType() == Krb5.PA_FOR_USER) { 700 if (options.containsKey(Option.ALLOW_S4U2SELF)) { 701 PAForUserEnc p4u = new PAForUserEnc( 702 new DerValue(pa.getValue()), null); 703 forUserCName = p4u.name; 704 System.out.println(realm + "> presenting a PA_FOR_USER " 705 + " in the name of " + p4u.name); 706 } 707 } 708 } 709 if (forUserCName != null) { 710 List<String> names = (List<String>)options.get(Option.ALLOW_S4U2SELF); 711 if (!names.contains(cname.toString())) { 712 // Mimic the normal KDC behavior. When a server is not 713 // allowed to send S4U2self, do not send an error. 714 // Instead, send a ticket which is useless later. 715 allowForwardable = false; 716 } 717 cname = forUserCName; 718 } 719 if (tkt == null) { 720 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 721 } 722 } 723 724 // Session key for original ticket, TGT 725 EncryptionKey ckey = etp.key; 726 727 // Session key for session with the service 728 EncryptionKey key = generateRandomKey(e2); 729 730 // Check time, TODO 731 KerberosTime till = body.till; 732 if (till == null) { 733 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO 734 } else if (till.isZero()) { 735 till = new KerberosTime(new Date().getTime() + 1000 * DEFAULT_LIFETIME); 736 } 737 738 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; 739 if (body.kdcOptions.get(KDCOptions.FORWARDABLE) 740 && allowForwardable) { 741 List<String> sensitives = (List<String>) 742 options.get(Option.SENSITIVE_ACCOUNTS); 743 if (sensitives != null && sensitives.contains(cname.toString())) { 744 // Cannot make FORWARDABLE 745 } else { 746 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; 747 } 748 } 749 // We do not request for addresses for FORWARDED tickets 750 if (options.containsKey(Option.CHECK_ADDRESSES) 751 && body.kdcOptions.get(KDCOptions.FORWARDED) 752 && body.addresses != null) { 753 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 754 } 755 if (body.kdcOptions.get(KDCOptions.FORWARDED) || 756 etp.flags.get(Krb5.TKT_OPTS_FORWARDED)) { 757 bFlags[Krb5.TKT_OPTS_FORWARDED] = true; 758 } 759 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { 760 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 761 //renew = new KerberosTime(new Date().getTime() + 1000 * 3600 * 24 * 7); 762 } 763 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { 764 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; 765 } 766 if (body.kdcOptions.get(KDCOptions.POSTDATED)) { 767 bFlags[Krb5.TKT_OPTS_POSTDATED] = true; 768 } 769 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { 770 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; 771 } 772 if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT)) { 773 if (!options.containsKey(Option.ALLOW_S4U2PROXY)) { 774 // Don't understand CNAME_IN_ADDL_TKT 775 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 776 } else { 777 Map<String,List<String>> map = (Map<String,List<String>>) 778 options.get(Option.ALLOW_S4U2PROXY); 779 Ticket second = KDCReqBodyDotFirstAdditionalTicket(body); 780 EncryptionKey key2 = keyForUser(second.sname, second.encPart.getEType(), true); 781 byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET); 782 DerInputStream derIn = new DerInputStream(bb); 783 DerValue der = derIn.getDerValue(); 784 EncTicketPart tktEncPart = new EncTicketPart(der.toByteArray()); 785 if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)) { 786 //throw new KrbException(Krb5.KDC_ERR_BADOPTION); 787 } 788 PrincipalName client = tktEncPart.cname; 789 System.out.println(realm + "> and an additional ticket of " 790 + client + " to " + second.sname); 791 if (map.containsKey(cname.toString())) { 792 if (map.get(cname.toString()).contains(service.toString())) { 793 System.out.println(realm + "> S4U2proxy OK"); 794 } else { 795 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 796 } 797 } else { 798 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 799 } 800 cname = client; 801 } 802 } 803 804 String okAsDelegate = (String)options.get(Option.OK_AS_DELEGATE); 805 if (okAsDelegate != null && ( 806 okAsDelegate.isEmpty() || 807 okAsDelegate.contains(service.getNameString()))) { 808 bFlags[Krb5.TKT_OPTS_DELEGATE] = true; 809 } 810 bFlags[Krb5.TKT_OPTS_INITIAL] = true; 811 812 KerberosTime renewTill = etp.renewTill; 813 if (renewTill != null && body.kdcOptions.get(KDCOptions.RENEW)) { 814 // till should never pass renewTill 815 if (till.greaterThan(renewTill)) { 816 till = renewTill; 817 } 818 if (System.getProperty("test.set.null.renew") != null) { 819 // Testing 8186576, see NullRenewUntil.java. 820 renewTill = null; 821 } 822 } 823 824 TicketFlags tFlags = new TicketFlags(bFlags); 825 EncTicketPart enc = new EncTicketPart( 826 tFlags, 827 key, 828 cname, 829 new TransitedEncoding(1, new byte[0]), // TODO 830 new KerberosTime(new Date()), 831 body.from, 832 till, renewTill, 833 body.addresses != null ? body.addresses 834 : etp.caddr, 835 null); 836 EncryptionKey skey = keyForUser(service, e3, true); 837 if (skey == null) { 838 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO 839 } 840 Ticket t = new Ticket( 841 System.getProperty("test.kdc.diff.sname") != null ? 842 new PrincipalName("xx" + service.toString()) : 843 service, 844 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET) 845 ); 846 EncTGSRepPart enc_part = new EncTGSRepPart( 847 key, 848 new LastReq(new LastReqEntry[]{ 849 new LastReqEntry(0, new KerberosTime(new Date().getTime() - 10000)) 850 }), 851 body.getNonce(), // TODO: detect replay 852 new KerberosTime(new Date().getTime() + 1000 * 3600 * 24), 853 // Next 5 and last MUST be same with ticket 854 tFlags, 855 new KerberosTime(new Date()), 856 body.from, 857 till, renewTill, 858 service, 859 body.addresses 860 ); 861 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY); 862 TGSRep tgsRep = new TGSRep(null, 863 cname, 864 t, 865 edata); 866 System.out.println(" Return " + tgsRep.cname 867 + " ticket for " + tgsRep.ticket.sname + ", flags " 868 + tFlags); 869 870 DerOutputStream out = new DerOutputStream(); 871 out.write(DerValue.createTag(DerValue.TAG_APPLICATION, 872 true, (byte)Krb5.KRB_TGS_REP), tgsRep.asn1Encode()); 873 return out.toByteArray(); 874 } catch (KrbException ke) { 875 ke.printStackTrace(System.out); 876 KRBError kerr = ke.getError(); 877 KDCReqBody body = tgsReq.reqBody; 878 System.out.println(" Error " + ke.returnCode() 879 + " " +ke.returnCodeMessage()); 880 if (kerr == null) { 881 kerr = new KRBError(null, null, null, 882 new KerberosTime(new Date()), 883 0, 884 ke.returnCode(), 885 body.cname, 886 service, 887 KrbException.errorMessage(ke.returnCode()), 888 null); 889 } 890 return kerr.asn1Encode(); 891 } 892 } 893 894 /** 895 * Processes a AS_REQ and generates a AS_REP (or KRB_ERROR) 896 * @param in the request 897 * @return the response 898 * @throws java.lang.Exception for various errors 899 */ 900 protected byte[] processAsReq(byte[] in) throws Exception { 901 ASReq asReq = new ASReq(in); 902 int[] eTypes = null; 903 List<PAData> outPAs = new ArrayList<>(); 904 905 PrincipalName service = asReq.reqBody.sname; 906 if (options.containsKey(KDC.Option.RESP_NT)) { 907 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT), 908 service.getNameStrings(), 909 Realm.getDefault()); 910 } 911 try { 912 System.out.println(realm + "> " + asReq.reqBody.cname + 913 " sends AS-REQ for " + 914 service + ", " + asReq.reqBody.kdcOptions); 915 916 KDCReqBody body = asReq.reqBody; 917 918 eTypes = KDCReqBodyDotEType(body); 919 int eType = eTypes[0]; 920 921 // Maybe server does not support aes256, but a kinit does 922 if (!EType.isSupported(eType)) { 923 if (eTypes.length < 2) { 924 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 925 } 926 eType = eTypes[1]; 927 } 928 929 EncryptionKey ckey = keyForUser(body.cname, eType, false); 930 EncryptionKey skey = keyForUser(service, eType, true); 931 932 if (options.containsKey(KDC.Option.ONLY_RC4_TGT)) { 933 int tgtEType = EncryptedData.ETYPE_ARCFOUR_HMAC; 934 boolean found = false; 935 for (int i=0; i<eTypes.length; i++) { 936 if (eTypes[i] == tgtEType) { 937 found = true; 938 break; 939 } 940 } 941 if (!found) { 942 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 943 } 944 skey = keyForUser(service, tgtEType, true); 945 } 946 if (ckey == null) { 947 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 948 } 949 if (skey == null) { 950 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO 951 } 952 953 // Session key 954 EncryptionKey key = generateRandomKey(eType); 955 // Check time, TODO 956 KerberosTime till = body.till; 957 KerberosTime rtime = body.rtime; 958 if (till == null) { 959 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO 960 } else if (till.isZero()) { 961 till = new KerberosTime( 962 new Date().getTime() + 1000 * DEFAULT_LIFETIME); 963 } else if (till.greaterThan(new KerberosTime(Instant.now() 964 .plus(1, ChronoUnit.DAYS)))) { 965 // If till is more than 1 day later, make it renewable 966 till = new KerberosTime( 967 new Date().getTime() + 1000 * DEFAULT_LIFETIME); 968 body.kdcOptions.set(KDCOptions.RENEWABLE, true); 969 if (rtime == null) rtime = till; 970 } 971 if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) { 972 rtime = new KerberosTime( 973 new Date().getTime() + 1000 * DEFAULT_RENEWTIME); 974 } 975 //body.from 976 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; 977 if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) { 978 List<String> sensitives = (List<String>) 979 options.get(Option.SENSITIVE_ACCOUNTS); 980 if (sensitives != null && sensitives.contains(body.cname.toString())) { 981 // Cannot make FORWARDABLE 982 } else { 983 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; 984 } 985 } 986 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { 987 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 988 //renew = new KerberosTime(new Date().getTime() + 1000 * 3600 * 24 * 7); 989 } 990 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { 991 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; 992 } 993 if (body.kdcOptions.get(KDCOptions.POSTDATED)) { 994 bFlags[Krb5.TKT_OPTS_POSTDATED] = true; 995 } 996 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { 997 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; 998 } 999 bFlags[Krb5.TKT_OPTS_INITIAL] = true; 1000 1001 // Creating PA-DATA 1002 DerValue[] pas2 = null, pas = null; 1003 if (options.containsKey(KDC.Option.DUP_ETYPE)) { 1004 int n = (Integer)options.get(KDC.Option.DUP_ETYPE); 1005 switch (n) { 1006 case 1: // customer's case in 7067974 1007 pas2 = new DerValue[] { 1008 new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), 1009 new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), 1010 new DerValue(new ETypeInfo2(1, realm, new byte[]{1}).asn1Encode()), 1011 }; 1012 pas = new DerValue[] { 1013 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1014 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1015 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1016 }; 1017 break; 1018 case 2: // we still reject non-null s2kparams and prefer E2 over E 1019 pas2 = new DerValue[] { 1020 new DerValue(new ETypeInfo2(1, realm, new byte[]{1}).asn1Encode()), 1021 new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), 1022 new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), 1023 }; 1024 pas = new DerValue[] { 1025 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1026 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1027 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1028 }; 1029 break; 1030 case 3: // but only E is wrong 1031 pas = new DerValue[] { 1032 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1033 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1034 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1035 }; 1036 break; 1037 case 4: // we also ignore rc4-hmac 1038 pas = new DerValue[] { 1039 new DerValue(new ETypeInfo(23, "ANYTHING").asn1Encode()), 1040 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1041 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1042 }; 1043 break; 1044 case 5: // "" should be wrong, but we accept it now 1045 // See s.s.k.internal.PAData$SaltAndParams 1046 pas = new DerValue[] { 1047 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1048 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1049 }; 1050 break; 1051 } 1052 } else { 1053 int[] epas = eTypes; 1054 if (options.containsKey(KDC.Option.RC4_FIRST_PREAUTH)) { 1055 for (int i=1; i<epas.length; i++) { 1056 if (epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC) { 1057 epas[i] = epas[0]; 1058 epas[0] = EncryptedData.ETYPE_ARCFOUR_HMAC; 1059 break; 1060 } 1061 }; 1062 } else if (options.containsKey(KDC.Option.ONLY_ONE_PREAUTH)) { 1063 epas = new int[] { eTypes[0] }; 1064 } 1065 pas2 = new DerValue[epas.length]; 1066 for (int i=0; i<epas.length; i++) { 1067 pas2[i] = new DerValue(new ETypeInfo2( 1068 epas[i], 1069 epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ? 1070 null : getSalt(body.cname), 1071 null).asn1Encode()); 1072 } 1073 boolean allOld = true; 1074 for (int i: eTypes) { 1075 if (i == EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96 || 1076 i == EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96) { 1077 allOld = false; 1078 break; 1079 } 1080 } 1081 if (allOld) { 1082 pas = new DerValue[epas.length]; 1083 for (int i=0; i<epas.length; i++) { 1084 pas[i] = new DerValue(new ETypeInfo( 1085 epas[i], 1086 epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ? 1087 null : getSalt(body.cname) 1088 ).asn1Encode()); 1089 } 1090 } 1091 } 1092 1093 DerOutputStream eid; 1094 if (pas2 != null) { 1095 eid = new DerOutputStream(); 1096 eid.putSequence(pas2); 1097 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO2, eid.toByteArray())); 1098 } 1099 if (pas != null) { 1100 eid = new DerOutputStream(); 1101 eid.putSequence(pas); 1102 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray())); 1103 } 1104 1105 PAData[] inPAs = KDCReqDotPAData(asReq); 1106 if (inPAs == null || inPAs.length == 0) { 1107 Object preauth = options.get(Option.PREAUTH_REQUIRED); 1108 if (preauth == null || preauth.equals(Boolean.TRUE)) { 1109 throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED); 1110 } 1111 } else { 1112 try { 1113 EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue())); 1114 EncryptionKey pakey = keyForUser(body.cname, data.getEType(), false); 1115 data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS); 1116 } catch (Exception e) { 1117 throw new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED); 1118 } 1119 bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true; 1120 } 1121 1122 TicketFlags tFlags = new TicketFlags(bFlags); 1123 EncTicketPart enc = new EncTicketPart( 1124 tFlags, 1125 key, 1126 body.cname, 1127 new TransitedEncoding(1, new byte[0]), 1128 new KerberosTime(new Date()), 1129 body.from, 1130 till, rtime, 1131 body.addresses, 1132 null); 1133 Ticket t = new Ticket( 1134 service, 1135 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET) 1136 ); 1137 EncASRepPart enc_part = new EncASRepPart( 1138 key, 1139 new LastReq(new LastReqEntry[]{ 1140 new LastReqEntry(0, new KerberosTime(new Date().getTime() - 10000)) 1141 }), 1142 body.getNonce(), // TODO: detect replay? 1143 new KerberosTime(new Date().getTime() + 1000 * 3600 * 24), 1144 // Next 5 and last MUST be same with ticket 1145 tFlags, 1146 new KerberosTime(new Date()), 1147 body.from, 1148 till, rtime, 1149 service, 1150 body.addresses 1151 ); 1152 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_AS_REP_PART); 1153 ASRep asRep = new ASRep( 1154 outPAs.toArray(new PAData[outPAs.size()]), 1155 body.cname, 1156 t, 1157 edata); 1158 1159 System.out.println(" Return " + asRep.cname 1160 + " ticket for " + asRep.ticket.sname + ", flags " 1161 + tFlags); 1162 1163 DerOutputStream out = new DerOutputStream(); 1164 out.write(DerValue.createTag(DerValue.TAG_APPLICATION, 1165 true, (byte)Krb5.KRB_AS_REP), asRep.asn1Encode()); 1166 byte[] result = out.toByteArray(); 1167 1168 // Added feature: 1169 // Write the current issuing TGT into a ccache file specified 1170 // by the system property below. 1171 String ccache = System.getProperty("test.kdc.save.ccache"); 1172 if (ccache != null) { 1173 asRep.encKDCRepPart = enc_part; 1174 sun.security.krb5.internal.ccache.Credentials credentials = 1175 new sun.security.krb5.internal.ccache.Credentials(asRep); 1176 CredentialsCache cache = 1177 CredentialsCache.create(asReq.reqBody.cname, ccache); 1178 if (cache == null) { 1179 throw new IOException("Unable to create the cache file " + 1180 ccache); 1181 } 1182 cache.update(credentials); 1183 cache.save(); 1184 } 1185 1186 return result; 1187 } catch (KrbException ke) { 1188 ke.printStackTrace(System.out); 1189 KRBError kerr = ke.getError(); 1190 KDCReqBody body = asReq.reqBody; 1191 System.out.println(" Error " + ke.returnCode() 1192 + " " +ke.returnCodeMessage()); 1193 byte[] eData = null; 1194 if (kerr == null) { 1195 if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED || 1196 ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) { 1197 DerOutputStream bytes = new DerOutputStream(); 1198 bytes.write(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]).asn1Encode()); 1199 for (PAData p: outPAs) { 1200 bytes.write(p.asn1Encode()); 1201 } 1202 DerOutputStream temp = new DerOutputStream(); 1203 temp.write(DerValue.tag_Sequence, bytes); 1204 eData = temp.toByteArray(); 1205 } 1206 kerr = new KRBError(null, null, null, 1207 new KerberosTime(new Date()), 1208 0, 1209 ke.returnCode(), 1210 body.cname, 1211 service, 1212 KrbException.errorMessage(ke.returnCode()), 1213 eData); 1214 } 1215 return kerr.asn1Encode(); 1216 } 1217 } 1218 1219 /** 1220 * Generates a line for a KDC to put inside [realms] of krb5.conf 1221 * @return REALM.NAME = { kdc = host:port etc } 1222 */ 1223 private String realmLine() { 1224 StringBuilder sb = new StringBuilder(); 1225 sb.append(realm).append(" = {\n kdc = ") 1226 .append(kdc).append(':').append(port).append('\n'); 1227 for (String s: conf) { 1228 sb.append(" ").append(s).append('\n'); 1229 } 1230 return sb.append("}\n").toString(); 1231 } 1232 1233 /** 1234 * Start the KDC service. This server listens on both UDP and TCP using 1235 * the same port number. It uses three threads to deal with requests. 1236 * They can be set to daemon threads if requested. 1237 * @param port the port number to listen to. If zero, a random available 1238 * port no less than 8000 will be chosen and used. 1239 * @param asDaemon true if the KDC threads should be daemons 1240 * @throws java.io.IOException for any communication error 1241 */ 1242 protected void startServer(int port, boolean asDaemon) throws IOException { 1243 if (port > 0) { 1244 u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1")); 1245 t1 = new ServerSocket(port); 1246 } else { 1247 while (true) { 1248 // Try to find a port number that's both TCP and UDP free 1249 try { 1250 port = 8000 + new java.util.Random().nextInt(10000); 1251 u1 = null; 1252 u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1")); 1253 t1 = new ServerSocket(port); 1254 break; 1255 } catch (Exception e) { 1256 if (u1 != null) u1.close(); 1257 } 1258 } 1259 } 1260 final DatagramSocket udp = u1; 1261 final ServerSocket tcp = t1; 1262 System.out.println("Start KDC on " + port); 1263 1264 this.port = port; 1265 1266 // The UDP consumer 1267 thread1 = new Thread() { 1268 public void run() { 1269 udpConsumerReady = true; 1270 while (true) { 1271 try { 1272 byte[] inbuf = new byte[8192]; 1273 DatagramPacket p = new DatagramPacket(inbuf, inbuf.length); 1274 udp.receive(p); 1275 System.out.println("-----------------------------------------------"); 1276 System.out.println(">>>>> UDP packet received"); 1277 q.put(new Job(processMessage(Arrays.copyOf(inbuf, p.getLength())), udp, p)); 1278 } catch (Exception e) { 1279 e.printStackTrace(); 1280 } 1281 } 1282 } 1283 }; 1284 thread1.setDaemon(asDaemon); 1285 thread1.start(); 1286 1287 // The TCP consumer 1288 thread2 = new Thread() { 1289 public void run() { 1290 tcpConsumerReady = true; 1291 while (true) { 1292 try { 1293 Socket socket = tcp.accept(); 1294 System.out.println("-----------------------------------------------"); 1295 System.out.println(">>>>> TCP connection established"); 1296 DataInputStream in = new DataInputStream(socket.getInputStream()); 1297 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); 1298 int len = in.readInt(); 1299 if (len > 65535) { 1300 throw new Exception("Huge request not supported"); 1301 } 1302 byte[] token = new byte[len]; 1303 in.readFully(token); 1304 q.put(new Job(processMessage(token), socket, out)); 1305 } catch (Exception e) { 1306 e.printStackTrace(); 1307 } 1308 } 1309 } 1310 }; 1311 thread2.setDaemon(asDaemon); 1312 thread2.start(); 1313 1314 // The dispatcher 1315 thread3 = new Thread() { 1316 public void run() { 1317 dispatcherReady = true; 1318 while (true) { 1319 try { 1320 q.take().send(); 1321 } catch (Exception e) { 1322 } 1323 } 1324 } 1325 }; 1326 thread3.setDaemon(true); 1327 thread3.start(); 1328 1329 // wait for the KDC is ready 1330 try { 1331 while (!isReady()) { 1332 Thread.sleep(100); 1333 } 1334 } catch(InterruptedException e) { 1335 throw new IOException(e); 1336 } 1337 } 1338 1339 boolean isReady() { 1340 return udpConsumerReady && tcpConsumerReady && dispatcherReady; 1341 } 1342 1343 public void terminate() { 1344 try { 1345 thread1.stop(); 1346 thread2.stop(); 1347 thread3.stop(); 1348 u1.close(); 1349 t1.close(); 1350 } catch (Exception e) { 1351 // OK 1352 } 1353 } 1354 1355 public static KDC startKDC(final String host, final String krbConfFileName, 1356 final String realm, final Map<String, String> principals, 1357 final String ktab, final KtabMode mode) { 1358 1359 KDC kdc; 1360 try { 1361 kdc = KDC.create(realm, host, 0, true); 1362 kdc.setOption(KDC.Option.PREAUTH_REQUIRED, Boolean.FALSE); 1363 if (krbConfFileName != null) { 1364 KDC.saveConfig(krbConfFileName, kdc); 1365 } 1366 1367 // Add principals 1368 if (principals != null) { 1369 principals.forEach((name, password) -> { 1370 if (password == null || password.isEmpty()) { 1371 System.out.println(String.format( 1372 "KDC:add a principal '%s' with a random " + 1373 "password", name)); 1374 kdc.addPrincipalRandKey(name); 1375 } else { 1376 System.out.println(String.format( 1377 "KDC:add a principal '%s' with '%s' password", 1378 name, password)); 1379 kdc.addPrincipal(name, password.toCharArray()); 1380 } 1381 }); 1382 } 1383 1384 // Create or append keys to existing keytab file 1385 if (ktab != null) { 1386 File ktabFile = new File(ktab); 1387 switch(mode) { 1388 case APPEND: 1389 if (ktabFile.exists()) { 1390 System.out.println(String.format( 1391 "KDC:append keys to an exising keytab " 1392 + "file %s", ktab)); 1393 kdc.appendKtab(ktab); 1394 } else { 1395 System.out.println(String.format( 1396 "KDC:create a new keytab file %s", ktab)); 1397 kdc.writeKtab(ktab); 1398 } 1399 break; 1400 case EXISTING: 1401 System.out.println(String.format( 1402 "KDC:use an existing keytab file %s", ktab)); 1403 break; 1404 default: 1405 throw new RuntimeException(String.format( 1406 "KDC:unsupported keytab mode: %s", mode)); 1407 } 1408 } 1409 1410 System.out.println(String.format( 1411 "KDC: started on %s:%s with '%s' realm", 1412 host, kdc.getPort(), realm)); 1413 } catch (Exception e) { 1414 throw new RuntimeException("KDC: unexpected exception", e); 1415 } 1416 1417 return kdc; 1418 } 1419 1420 /** 1421 * Helper class to encapsulate a job in a KDC. 1422 */ 1423 private static class Job { 1424 byte[] token; // The received request at creation time and 1425 // the response at send time 1426 Socket s; // The TCP socket from where the request comes 1427 DataOutputStream out; // The OutputStream of the TCP socket 1428 DatagramSocket s2; // The UDP socket from where the request comes 1429 DatagramPacket dp; // The incoming UDP datagram packet 1430 boolean useTCP; // Whether TCP or UDP is used 1431 1432 // Creates a job object for TCP 1433 Job(byte[] token, Socket s, DataOutputStream out) { 1434 useTCP = true; 1435 this.token = token; 1436 this.s = s; 1437 this.out = out; 1438 } 1439 1440 // Creates a job object for UDP 1441 Job(byte[] token, DatagramSocket s2, DatagramPacket dp) { 1442 useTCP = false; 1443 this.token = token; 1444 this.s2 = s2; 1445 this.dp = dp; 1446 } 1447 1448 // Sends the output back to the client 1449 void send() { 1450 try { 1451 if (useTCP) { 1452 System.out.println(">>>>> TCP request honored"); 1453 out.writeInt(token.length); 1454 out.write(token); 1455 s.close(); 1456 } else { 1457 System.out.println(">>>>> UDP request honored"); 1458 s2.send(new DatagramPacket(token, token.length, dp.getAddress(), dp.getPort())); 1459 } 1460 } catch (Exception e) { 1461 e.printStackTrace(); 1462 } 1463 } 1464 } 1465 1466 1467 // Calling private methods thru reflections 1468 private static final Field getPADataField; 1469 private static final Field getEType; 1470 private static final Constructor<EncryptedData> ctorEncryptedData; 1471 private static final Method stringToKey; 1472 private static final Field getAddlTkt; 1473 1474 static { 1475 try { 1476 ctorEncryptedData = EncryptedData.class.getDeclaredConstructor(DerValue.class); 1477 ctorEncryptedData.setAccessible(true); 1478 getPADataField = KDCReq.class.getDeclaredField("pAData"); 1479 getPADataField.setAccessible(true); 1480 getEType = KDCReqBody.class.getDeclaredField("eType"); 1481 getEType.setAccessible(true); 1482 stringToKey = EncryptionKey.class.getDeclaredMethod( 1483 "stringToKey", 1484 char[].class, String.class, byte[].class, Integer.TYPE); 1485 stringToKey.setAccessible(true); 1486 getAddlTkt = KDCReqBody.class.getDeclaredField("additionalTickets"); 1487 getAddlTkt.setAccessible(true); 1488 } catch (NoSuchFieldException nsfe) { 1489 throw new AssertionError(nsfe); 1490 } catch (NoSuchMethodException nsme) { 1491 throw new AssertionError(nsme); 1492 } 1493 } 1494 private EncryptedData newEncryptedData(DerValue der) { 1495 try { 1496 return ctorEncryptedData.newInstance(der); 1497 } catch (Exception e) { 1498 throw new AssertionError(e); 1499 } 1500 } 1501 private static PAData[] KDCReqDotPAData(KDCReq req) { 1502 try { 1503 return (PAData[])getPADataField.get(req); 1504 } catch (Exception e) { 1505 throw new AssertionError(e); 1506 } 1507 } 1508 private static int[] KDCReqBodyDotEType(KDCReqBody body) { 1509 try { 1510 return (int[]) getEType.get(body); 1511 } catch (Exception e) { 1512 throw new AssertionError(e); 1513 } 1514 } 1515 private static byte[] EncryptionKeyDotStringToKey(char[] password, String salt, 1516 byte[] s2kparams, int keyType) throws KrbCryptoException { 1517 try { 1518 return (byte[])stringToKey.invoke( 1519 null, password, salt, s2kparams, keyType); 1520 } catch (InvocationTargetException ex) { 1521 throw (KrbCryptoException)ex.getCause(); 1522 } catch (Exception e) { 1523 throw new AssertionError(e); 1524 } 1525 } 1526 private static Ticket KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body) { 1527 try { 1528 return ((Ticket[])getAddlTkt.get(body))[0]; 1529 } catch (Exception e) { 1530 throw new AssertionError(e); 1531 } 1532 } 1533} 1534