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 */ main(String[] args)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 */ create(String realm)274 public static KDC create(String realm) throws IOException { 275 return create(realm, "kdc." + realm.toLowerCase(Locale.US), 0, true); 276 } 277 existing(String realm, String kdc, int port)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 */ create(String realm, String kdc, int port, boolean asDaemon)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 */ setOption(Option key, Object value)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 */ writeKtab(String tab, boolean append, String... names)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 */ writeMultiKtab(String tab, KDC... kdcs)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 */ appendMultiKtab(String tab, KDC... kdcs)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 */ writeKtab(String tab)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 */ appendKtab(String tab)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 */ addPrincipal(String user, char[] pass)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 */ addPrincipal( String user, char[] pass, String salt, byte[] s2kparams)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 */ addPrincipalRandKey(String user)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 */ getRealm()456 public String getRealm() { 457 return realm; 458 } 459 460 /** 461 * Returns the name of kdc 462 * @return the name of kdc 463 */ getKDC()464 public String getKDC() { 465 return kdc; 466 } 467 468 /** 469 * Add realm-specific krb5.conf setting 470 */ addConf(String s)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 */ saveConfig(String file, KDC kdc, Object... more)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 */ getPort()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 */ registerAlias(String alias, KDC referredKDC)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 */ registerAlias(String alias, String user)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 */ KDC(String realm, String kdc)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 */ KDC(String realm, String kdc, int port, boolean asDaemon)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 */ randomPassword()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 */ generateRandomKey(int eType)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 */ getPassword(PrincipalName p, boolean server)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 */ getSalt(PrincipalName p)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 */ getParams(PrincipalName p, int etype)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 */ keyForUser(PrincipalName p, int etype, boolean server)723 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 */ timeAfter(int offset)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 */ genKey0( char[] pass, String salt, byte[] s2kparams, int etype, Integer kvno)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 */ processMessage(byte[] in)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 */ processTgsReq(byte[] in)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 = tgsReq.pAData; 805 806 Ticket tkt = null; 807 EncTicketPart etp = null; 808 809 PrincipalName cname = null; 810 boolean allowForwardable = true; 811 boolean isReferral = false; 812 if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) { 813 System.out.println(realm + "> verifying referral for " + 814 body.sname.getNameString()); 815 KDC referral = aliasReferrals.get(body.sname.getNameString()); 816 if (referral != null) { 817 service = new PrincipalName( 818 PrincipalName.TGS_DEFAULT_SRV_NAME + 819 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + 820 referral.getRealm(), PrincipalName.KRB_NT_SRV_INST, 821 this.getRealm()); 822 System.out.println(realm + "> referral to " + 823 referral.getRealm()); 824 isReferral = true; 825 } 826 } 827 828 if (pas == null || pas.length == 0) { 829 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 830 } else { 831 PrincipalName forUserCName = null; 832 for (PAData pa: pas) { 833 if (pa.getType() == Krb5.PA_TGS_REQ) { 834 APReq apReq = new APReq(pa.getValue()); 835 tkt = apReq.ticket; 836 int te = tkt.encPart.getEType(); 837 EncryptionKey kkey = keyForUser(tkt.sname, te, true); 838 byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET); 839 DerInputStream derIn = new DerInputStream(bb); 840 DerValue der = derIn.getDerValue(); 841 etp = new EncTicketPart(der.toByteArray()); 842 // Finally, cname will be overwritten by PA-FOR-USER 843 // if it exists. 844 cname = etp.cname; 845 System.out.println(realm + "> presenting a ticket of " 846 + etp.cname + " to " + tkt.sname); 847 } else if (pa.getType() == Krb5.PA_FOR_USER) { 848 if (options.containsKey(Option.ALLOW_S4U2SELF)) { 849 PAForUserEnc p4u = new PAForUserEnc( 850 new DerValue(pa.getValue()), null); 851 forUserCName = p4u.name; 852 System.out.println(realm + "> See PA_FOR_USER " 853 + " in the name of " + p4u.name); 854 } 855 } 856 } 857 if (forUserCName != null) { 858 List<String> names = (List<String>) 859 options.get(Option.ALLOW_S4U2SELF); 860 if (!names.contains(cname.toString())) { 861 // Mimic the normal KDC behavior. When a server is not 862 // allowed to send S4U2self, do not send an error. 863 // Instead, send a ticket which is useless later. 864 allowForwardable = false; 865 } 866 cname = forUserCName; 867 } 868 if (tkt == null) { 869 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 870 } 871 } 872 873 // Session key for original ticket, TGT 874 EncryptionKey ckey = etp.key; 875 876 // Session key for session with the service 877 EncryptionKey key = generateRandomKey(e2); 878 879 // Check time, TODO 880 KerberosTime from = body.from; 881 KerberosTime till = body.till; 882 if (from == null || from.isZero()) { 883 from = timeAfter(0); 884 } 885 if (till == null) { 886 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO 887 } else if (till.isZero()) { 888 till = timeAfter(DEFAULT_LIFETIME); 889 } 890 891 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; 892 if (body.kdcOptions.get(KDCOptions.FORWARDABLE) 893 && allowForwardable) { 894 List<String> sensitives = (List<String>) 895 options.get(Option.SENSITIVE_ACCOUNTS); 896 if (sensitives != null && sensitives.contains(cname.toString())) { 897 // Cannot make FORWARDABLE 898 } else { 899 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; 900 } 901 } 902 // We do not request for addresses for FORWARDED tickets 903 if (options.containsKey(Option.CHECK_ADDRESSES) 904 && body.kdcOptions.get(KDCOptions.FORWARDED) 905 && body.addresses != null) { 906 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 907 } 908 if (body.kdcOptions.get(KDCOptions.FORWARDED) || 909 etp.flags.get(Krb5.TKT_OPTS_FORWARDED)) { 910 bFlags[Krb5.TKT_OPTS_FORWARDED] = true; 911 } 912 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { 913 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 914 //renew = timeAfter(3600 * 24 * 7); 915 } 916 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { 917 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; 918 } 919 if (body.kdcOptions.get(KDCOptions.POSTDATED)) { 920 bFlags[Krb5.TKT_OPTS_POSTDATED] = true; 921 } 922 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { 923 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; 924 } 925 if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT) && 926 !isReferral) { 927 if (!options.containsKey(Option.ALLOW_S4U2PROXY)) { 928 // Don't understand CNAME_IN_ADDL_TKT 929 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 930 } else { 931 Map<String,List<String>> map = (Map<String,List<String>>) 932 options.get(Option.ALLOW_S4U2PROXY); 933 Ticket second = KDCReqBodyDotFirstAdditionalTicket(body); 934 EncryptionKey key2 = keyForUser( 935 second.sname, second.encPart.getEType(), true); 936 byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET); 937 DerInputStream derIn = new DerInputStream(bb); 938 DerValue der = derIn.getDerValue(); 939 EncTicketPart tktEncPart = new EncTicketPart(der.toByteArray()); 940 if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)) { 941 //throw new KrbException(Krb5.KDC_ERR_BADOPTION); 942 } 943 PrincipalName client = tktEncPart.cname; 944 System.out.println(realm + "> and an additional ticket of " 945 + client + " to " + second.sname); 946 if (map.containsKey(cname.toString())) { 947 if (map.get(cname.toString()).contains(service.toString())) { 948 System.out.println(realm + "> S4U2proxy OK"); 949 } else { 950 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 951 } 952 } else { 953 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 954 } 955 cname = client; 956 } 957 } 958 959 String okAsDelegate = (String)options.get(Option.OK_AS_DELEGATE); 960 if (okAsDelegate != null && ( 961 okAsDelegate.isEmpty() || 962 okAsDelegate.contains(service.getNameString()))) { 963 bFlags[Krb5.TKT_OPTS_DELEGATE] = true; 964 } 965 bFlags[Krb5.TKT_OPTS_INITIAL] = true; 966 967 KerberosTime renewTill = etp.renewTill; 968 if (renewTill != null && body.kdcOptions.get(KDCOptions.RENEW)) { 969 // till should never pass renewTill 970 if (till.greaterThan(renewTill)) { 971 till = renewTill; 972 } 973 if (System.getProperty("test.set.null.renew") != null) { 974 // Testing 8186576, see NullRenewUntil.java. 975 renewTill = null; 976 } 977 } 978 979 TicketFlags tFlags = new TicketFlags(bFlags); 980 EncTicketPart enc = new EncTicketPart( 981 tFlags, 982 key, 983 cname, 984 new TransitedEncoding(1, new byte[0]), // TODO 985 timeAfter(0), 986 from, 987 till, renewTill, 988 body.addresses != null ? body.addresses 989 : etp.caddr, 990 null); 991 EncryptionKey skey = keyForUser(service, e3, true); 992 if (skey == null) { 993 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO 994 } 995 Ticket t = new Ticket( 996 System.getProperty("test.kdc.diff.sname") != null ? 997 new PrincipalName("xx" + service.toString()) : 998 service, 999 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET) 1000 ); 1001 EncTGSRepPart enc_part = new EncTGSRepPart( 1002 key, 1003 new LastReq(new LastReqEntry[] { 1004 new LastReqEntry(0, timeAfter(-10)) 1005 }), 1006 body.getNonce(), // TODO: detect replay 1007 timeAfter(3600 * 24), 1008 // Next 5 and last MUST be same with ticket 1009 tFlags, 1010 timeAfter(0), 1011 from, 1012 till, renewTill, 1013 service, 1014 body.addresses, 1015 null 1016 ); 1017 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), 1018 KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY); 1019 TGSRep tgsRep = new TGSRep(null, 1020 cname, 1021 t, 1022 edata); 1023 System.out.println(" Return " + tgsRep.cname 1024 + " ticket for " + tgsRep.ticket.sname + ", flags " 1025 + tFlags); 1026 1027 DerOutputStream out = new DerOutputStream(); 1028 out.write(DerValue.createTag(DerValue.TAG_APPLICATION, 1029 true, (byte)Krb5.KRB_TGS_REP), tgsRep.asn1Encode()); 1030 return out.toByteArray(); 1031 } catch (KrbException ke) { 1032 ke.printStackTrace(System.out); 1033 KRBError kerr = ke.getError(); 1034 KDCReqBody body = tgsReq.reqBody; 1035 System.out.println(" Error " + ke.returnCode() 1036 + " " +ke.returnCodeMessage()); 1037 if (kerr == null) { 1038 kerr = new KRBError(null, null, null, 1039 timeAfter(0), 1040 0, 1041 ke.returnCode(), 1042 body.cname, 1043 service, 1044 KrbException.errorMessage(ke.returnCode()), 1045 null); 1046 } 1047 return kerr.asn1Encode(); 1048 } 1049 } 1050 1051 /** 1052 * Processes a AS_REQ and generates a AS_REP (or KRB_ERROR) 1053 * @param in the request 1054 * @return the response 1055 * @throws java.lang.Exception for various errors 1056 */ processAsReq(byte[] in)1057 protected byte[] processAsReq(byte[] in) throws Exception { 1058 ASReq asReq = new ASReq(in); 1059 byte[] asReqbytes = asReq.asn1Encode(); 1060 int[] eTypes = null; 1061 List<PAData> outPAs = new ArrayList<>(); 1062 1063 PrincipalName service = asReq.reqBody.sname; 1064 if (options.containsKey(KDC.Option.RESP_NT)) { 1065 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT), 1066 service.getNameStrings(), 1067 Realm.getDefault()); 1068 } 1069 try { 1070 System.out.println(realm + "> " + asReq.reqBody.cname + 1071 " sends AS-REQ for " + 1072 service + ", " + asReq.reqBody.kdcOptions); 1073 1074 KDCReqBody body = asReq.reqBody; 1075 1076 eTypes = filterSupported(KDCReqBodyDotEType(body)); 1077 if (eTypes.length == 0) { 1078 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1079 } 1080 int eType = eTypes[0]; 1081 1082 if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) { 1083 PrincipalName principal = alias2Principals.get( 1084 body.cname.getNameString()); 1085 if (principal != null) { 1086 body.cname = principal; 1087 } else { 1088 KDC referral = aliasReferrals.get(body.cname.getNameString()); 1089 if (referral != null) { 1090 body.cname = new PrincipalName( 1091 PrincipalName.TGS_DEFAULT_SRV_NAME, 1092 PrincipalName.KRB_NT_SRV_INST, 1093 referral.getRealm()); 1094 throw new KrbException(Krb5.KRB_ERR_WRONG_REALM); 1095 } 1096 } 1097 } 1098 1099 EncryptionKey ckey = keyForUser(body.cname, eType, false); 1100 EncryptionKey skey = keyForUser(service, eType, true); 1101 1102 if (options.containsKey(KDC.Option.ONLY_RC4_TGT)) { 1103 int tgtEType = EncryptedData.ETYPE_ARCFOUR_HMAC; 1104 boolean found = false; 1105 for (int i=0; i<eTypes.length; i++) { 1106 if (eTypes[i] == tgtEType) { 1107 found = true; 1108 break; 1109 } 1110 } 1111 if (!found) { 1112 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1113 } 1114 skey = keyForUser(service, tgtEType, true); 1115 } 1116 if (ckey == null) { 1117 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1118 } 1119 if (skey == null) { 1120 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO 1121 } 1122 1123 // Session key 1124 EncryptionKey key = generateRandomKey(eType); 1125 // Check time, TODO 1126 KerberosTime from = body.from; 1127 KerberosTime till = body.till; 1128 KerberosTime rtime = body.rtime; 1129 if (from == null || from.isZero()) { 1130 from = timeAfter(0); 1131 } 1132 if (till == null) { 1133 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO 1134 } else if (till.isZero()) { 1135 till = timeAfter(DEFAULT_LIFETIME); 1136 } else if (till.greaterThan(timeAfter(24 * 3600)) 1137 && System.getProperty("test.kdc.force.till") == null) { 1138 // If till is more than 1 day later, make it renewable 1139 till = timeAfter(DEFAULT_LIFETIME); 1140 body.kdcOptions.set(KDCOptions.RENEWABLE, true); 1141 if (rtime == null) rtime = till; 1142 } 1143 if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) { 1144 rtime = timeAfter(DEFAULT_RENEWTIME); 1145 } 1146 //body.from 1147 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; 1148 if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) { 1149 List<String> sensitives = (List<String>) 1150 options.get(Option.SENSITIVE_ACCOUNTS); 1151 if (sensitives != null 1152 && sensitives.contains(body.cname.toString())) { 1153 // Cannot make FORWARDABLE 1154 } else { 1155 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; 1156 } 1157 } 1158 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { 1159 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 1160 //renew = timeAfter(3600 * 24 * 7); 1161 } 1162 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { 1163 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; 1164 } 1165 if (body.kdcOptions.get(KDCOptions.POSTDATED)) { 1166 bFlags[Krb5.TKT_OPTS_POSTDATED] = true; 1167 } 1168 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { 1169 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; 1170 } 1171 bFlags[Krb5.TKT_OPTS_INITIAL] = true; 1172 1173 // Creating PA-DATA 1174 DerValue[] pas2 = null, pas = null; 1175 if (options.containsKey(KDC.Option.DUP_ETYPE)) { 1176 int n = (Integer)options.get(KDC.Option.DUP_ETYPE); 1177 switch (n) { 1178 case 1: // customer's case in 7067974 1179 pas2 = new DerValue[] { 1180 new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), 1181 new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), 1182 new DerValue(new ETypeInfo2( 1183 1, realm, new byte[]{1}).asn1Encode()), 1184 }; 1185 pas = new DerValue[] { 1186 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1187 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1188 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1189 }; 1190 break; 1191 case 2: // we still reject non-null s2kparams and prefer E2 over E 1192 pas2 = new DerValue[] { 1193 new DerValue(new ETypeInfo2( 1194 1, realm, new byte[]{1}).asn1Encode()), 1195 new DerValue(new ETypeInfo2(1, null, null).asn1Encode()), 1196 new DerValue(new ETypeInfo2(1, "", null).asn1Encode()), 1197 }; 1198 pas = new DerValue[] { 1199 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1200 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1201 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1202 }; 1203 break; 1204 case 3: // but only E is wrong 1205 pas = new DerValue[] { 1206 new DerValue(new ETypeInfo(1, realm).asn1Encode()), 1207 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1208 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1209 }; 1210 break; 1211 case 4: // we also ignore rc4-hmac 1212 pas = new DerValue[] { 1213 new DerValue(new ETypeInfo(23, "ANYTHING").asn1Encode()), 1214 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1215 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1216 }; 1217 break; 1218 case 5: // "" should be wrong, but we accept it now 1219 // See s.s.k.internal.PAData$SaltAndParams 1220 pas = new DerValue[] { 1221 new DerValue(new ETypeInfo(1, "").asn1Encode()), 1222 new DerValue(new ETypeInfo(1, null).asn1Encode()), 1223 }; 1224 break; 1225 } 1226 } else { 1227 int[] epas = eTypes; 1228 if (options.containsKey(KDC.Option.RC4_FIRST_PREAUTH)) { 1229 for (int i=1; i<epas.length; i++) { 1230 if (epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC) { 1231 epas[i] = epas[0]; 1232 epas[0] = EncryptedData.ETYPE_ARCFOUR_HMAC; 1233 break; 1234 } 1235 }; 1236 } else if (options.containsKey(KDC.Option.ONLY_ONE_PREAUTH)) { 1237 epas = new int[] { eTypes[0] }; 1238 } 1239 pas2 = new DerValue[epas.length]; 1240 for (int i=0; i<epas.length; i++) { 1241 pas2[i] = new DerValue(new ETypeInfo2( 1242 epas[i], 1243 epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ? 1244 null : getSalt(body.cname), 1245 getParams(body.cname, epas[i])).asn1Encode()); 1246 } 1247 boolean allOld = true; 1248 for (int i: eTypes) { 1249 if (i >= EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96 && 1250 i != EncryptedData.ETYPE_ARCFOUR_HMAC) { 1251 allOld = false; 1252 break; 1253 } 1254 } 1255 if (allOld) { 1256 pas = new DerValue[epas.length]; 1257 for (int i=0; i<epas.length; i++) { 1258 pas[i] = new DerValue(new ETypeInfo( 1259 epas[i], 1260 epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ? 1261 null : getSalt(body.cname) 1262 ).asn1Encode()); 1263 } 1264 } 1265 } 1266 1267 DerOutputStream eid; 1268 if (pas2 != null) { 1269 eid = new DerOutputStream(); 1270 eid.putSequence(pas2); 1271 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO2, eid.toByteArray())); 1272 } 1273 if (pas != null) { 1274 eid = new DerOutputStream(); 1275 eid.putSequence(pas); 1276 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray())); 1277 } 1278 1279 PAData[] inPAs = asReq.pAData; 1280 List<PAData> enc_outPAs = new ArrayList<>(); 1281 1282 byte[] paEncTimestamp = null; 1283 if (inPAs != null) { 1284 for (PAData inPA : inPAs) { 1285 if (inPA.getType() == Krb5.PA_ENC_TIMESTAMP) { 1286 paEncTimestamp = inPA.getValue(); 1287 } 1288 } 1289 } 1290 1291 if (paEncTimestamp == null) { 1292 Object preauth = options.get(Option.PREAUTH_REQUIRED); 1293 if (preauth == null || preauth.equals(Boolean.TRUE)) { 1294 throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED); 1295 } 1296 } else { 1297 EncryptionKey pakey = null; 1298 try { 1299 EncryptedData data = newEncryptedData( 1300 new DerValue(paEncTimestamp)); 1301 pakey = keyForUser(body.cname, data.getEType(), false); 1302 data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS); 1303 } catch (Exception e) { 1304 KrbException ke = new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED); 1305 ke.initCause(e); 1306 throw ke; 1307 } 1308 bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true; 1309 for (PAData pa : inPAs) { 1310 if (pa.getType() == Krb5.PA_REQ_ENC_PA_REP) { 1311 Checksum ckSum = new Checksum( 1312 Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128, 1313 asReqbytes, ckey, KeyUsage.KU_AS_REQ); 1314 enc_outPAs.add(new PAData(Krb5.PA_REQ_ENC_PA_REP, 1315 ckSum.asn1Encode())); 1316 bFlags[Krb5.TKT_OPTS_ENC_PA_REP] = true; 1317 break; 1318 } 1319 } 1320 } 1321 1322 TicketFlags tFlags = new TicketFlags(bFlags); 1323 EncTicketPart enc = new EncTicketPart( 1324 tFlags, 1325 key, 1326 body.cname, 1327 new TransitedEncoding(1, new byte[0]), 1328 timeAfter(0), 1329 from, 1330 till, rtime, 1331 body.addresses, 1332 null); 1333 Ticket t = new Ticket( 1334 service, 1335 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET) 1336 ); 1337 EncASRepPart enc_part = new EncASRepPart( 1338 key, 1339 new LastReq(new LastReqEntry[]{ 1340 new LastReqEntry(0, timeAfter(-10)) 1341 }), 1342 body.getNonce(), // TODO: detect replay? 1343 timeAfter(3600 * 24), 1344 // Next 5 and last MUST be same with ticket 1345 tFlags, 1346 timeAfter(0), 1347 from, 1348 till, rtime, 1349 service, 1350 body.addresses, 1351 enc_outPAs.toArray(new PAData[enc_outPAs.size()]) 1352 ); 1353 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), 1354 KeyUsage.KU_ENC_AS_REP_PART); 1355 ASRep asRep = new ASRep( 1356 outPAs.toArray(new PAData[outPAs.size()]), 1357 body.cname, 1358 t, 1359 edata); 1360 1361 System.out.println(" Return " + asRep.cname 1362 + " ticket for " + asRep.ticket.sname + ", flags " 1363 + tFlags); 1364 1365 DerOutputStream out = new DerOutputStream(); 1366 out.write(DerValue.createTag(DerValue.TAG_APPLICATION, 1367 true, (byte)Krb5.KRB_AS_REP), asRep.asn1Encode()); 1368 byte[] result = out.toByteArray(); 1369 1370 // Added feature: 1371 // Write the current issuing TGT into a ccache file specified 1372 // by the system property below. 1373 String ccache = System.getProperty("test.kdc.save.ccache"); 1374 if (ccache != null) { 1375 asRep.encKDCRepPart = enc_part; 1376 sun.security.krb5.internal.ccache.Credentials credentials = 1377 new sun.security.krb5.internal.ccache.Credentials(asRep); 1378 CredentialsCache cache = 1379 CredentialsCache.create(asReq.reqBody.cname, ccache); 1380 if (cache == null) { 1381 throw new IOException("Unable to create the cache file " + 1382 ccache); 1383 } 1384 cache.update(credentials); 1385 cache.save(); 1386 } 1387 1388 return result; 1389 } catch (KrbException ke) { 1390 ke.printStackTrace(System.out); 1391 KRBError kerr = ke.getError(); 1392 KDCReqBody body = asReq.reqBody; 1393 System.out.println(" Error " + ke.returnCode() 1394 + " " +ke.returnCodeMessage()); 1395 byte[] eData = null; 1396 if (kerr == null) { 1397 if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED || 1398 ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) { 1399 outPAs.add(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0])); 1400 } 1401 if (outPAs.size() > 0) { 1402 DerOutputStream bytes = new DerOutputStream(); 1403 for (PAData p: outPAs) { 1404 bytes.write(p.asn1Encode()); 1405 } 1406 DerOutputStream temp = new DerOutputStream(); 1407 temp.write(DerValue.tag_Sequence, bytes); 1408 eData = temp.toByteArray(); 1409 } 1410 kerr = new KRBError(null, null, null, 1411 timeAfter(0), 1412 0, 1413 ke.returnCode(), 1414 body.cname, 1415 service, 1416 KrbException.errorMessage(ke.returnCode()), 1417 eData); 1418 } 1419 return kerr.asn1Encode(); 1420 } 1421 } 1422 filterSupported(int[] input)1423 private int[] filterSupported(int[] input) { 1424 int count = 0; 1425 for (int i = 0; i < input.length; i++) { 1426 if (!EType.isSupported(input[i])) { 1427 continue; 1428 } 1429 if (SUPPORTED_ETYPES != null) { 1430 boolean supported = false; 1431 for (String se : SUPPORTED_ETYPES.split(",")) { 1432 if (Config.getType(se) == input[i]) { 1433 supported = true; 1434 break; 1435 } 1436 } 1437 if (!supported) { 1438 continue; 1439 } 1440 } 1441 if (count != i) { 1442 input[count] = input[i]; 1443 } 1444 count++; 1445 } 1446 if (count != input.length) { 1447 input = Arrays.copyOf(input, count); 1448 } 1449 return input; 1450 } 1451 1452 /** 1453 * Generates a line for a KDC to put inside [realms] of krb5.conf 1454 * @return REALM.NAME = { kdc = host:port etc } 1455 */ realmLine()1456 private String realmLine() { 1457 StringBuilder sb = new StringBuilder(); 1458 sb.append(realm).append(" = {\n kdc = ") 1459 .append(kdc).append(':').append(port).append('\n'); 1460 for (String s: conf) { 1461 sb.append(" ").append(s).append('\n'); 1462 } 1463 return sb.append("}\n").toString(); 1464 } 1465 1466 /** 1467 * Start the KDC service. This server listens on both UDP and TCP using 1468 * the same port number. It uses three threads to deal with requests. 1469 * They can be set to daemon threads if requested. 1470 * @param port the port number to listen to. If zero, a random available 1471 * port no less than 8000 will be chosen and used. 1472 * @param asDaemon true if the KDC threads should be daemons 1473 * @throws java.io.IOException for any communication error 1474 */ startServer(int port, boolean asDaemon)1475 protected void startServer(int port, boolean asDaemon) throws IOException { 1476 if (nativeKdc != null) { 1477 startNativeServer(port, asDaemon); 1478 } else { 1479 startJavaServer(port, asDaemon); 1480 } 1481 } 1482 startNativeServer(int port, boolean asDaemon)1483 private void startNativeServer(int port, boolean asDaemon) throws IOException { 1484 nativeKdc.prepare(); 1485 nativeKdc.init(); 1486 kdcProc = nativeKdc.kdc(); 1487 } 1488 startJavaServer(int port, boolean asDaemon)1489 private void startJavaServer(int port, boolean asDaemon) throws IOException { 1490 if (port > 0) { 1491 u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1")); 1492 t1 = new ServerSocket(port); 1493 } else { 1494 while (true) { 1495 // Try to find a port number that's both TCP and UDP free 1496 try { 1497 port = 8000 + new java.util.Random().nextInt(10000); 1498 u1 = null; 1499 u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1")); 1500 t1 = new ServerSocket(port); 1501 break; 1502 } catch (Exception e) { 1503 if (u1 != null) u1.close(); 1504 } 1505 } 1506 } 1507 final DatagramSocket udp = u1; 1508 final ServerSocket tcp = t1; 1509 System.out.println("Start KDC on " + port); 1510 1511 this.port = port; 1512 1513 // The UDP consumer 1514 thread1 = new Thread() { 1515 public void run() { 1516 udpConsumerReady = true; 1517 while (true) { 1518 try { 1519 byte[] inbuf = new byte[8192]; 1520 DatagramPacket p = new DatagramPacket(inbuf, inbuf.length); 1521 udp.receive(p); 1522 System.out.println("-----------------------------------------------"); 1523 System.out.println(">>>>> UDP packet received"); 1524 q.put(new Job(processMessage(Arrays.copyOf(inbuf, p.getLength())), udp, p)); 1525 } catch (Exception e) { 1526 e.printStackTrace(); 1527 } 1528 } 1529 } 1530 }; 1531 thread1.setDaemon(asDaemon); 1532 thread1.start(); 1533 1534 // The TCP consumer 1535 thread2 = new Thread() { 1536 public void run() { 1537 tcpConsumerReady = true; 1538 while (true) { 1539 try { 1540 Socket socket = tcp.accept(); 1541 System.out.println("-----------------------------------------------"); 1542 System.out.println(">>>>> TCP connection established"); 1543 DataInputStream in = new DataInputStream(socket.getInputStream()); 1544 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); 1545 int len = in.readInt(); 1546 if (len > 65535) { 1547 throw new Exception("Huge request not supported"); 1548 } 1549 byte[] token = new byte[len]; 1550 in.readFully(token); 1551 q.put(new Job(processMessage(token), socket, out)); 1552 } catch (Exception e) { 1553 e.printStackTrace(); 1554 } 1555 } 1556 } 1557 }; 1558 thread2.setDaemon(asDaemon); 1559 thread2.start(); 1560 1561 // The dispatcher 1562 thread3 = new Thread() { 1563 public void run() { 1564 dispatcherReady = true; 1565 while (true) { 1566 try { 1567 q.take().send(); 1568 } catch (Exception e) { 1569 } 1570 } 1571 } 1572 }; 1573 thread3.setDaemon(true); 1574 thread3.start(); 1575 1576 // wait for the KDC is ready 1577 try { 1578 while (!isReady()) { 1579 Thread.sleep(100); 1580 } 1581 } catch(InterruptedException e) { 1582 throw new IOException(e); 1583 } 1584 } 1585 kinit(String user, String ccache)1586 public void kinit(String user, String ccache) throws Exception { 1587 if (user.indexOf('@') < 0) { 1588 user = user + "@" + realm; 1589 } 1590 if (nativeKdc != null) { 1591 nativeKdc.kinit(user, ccache); 1592 } else { 1593 Context.fromUserPass(user, passwords.get(user), false) 1594 .ccache(ccache); 1595 } 1596 } 1597 isReady()1598 boolean isReady() { 1599 return udpConsumerReady && tcpConsumerReady && dispatcherReady; 1600 } 1601 terminate()1602 public void terminate() { 1603 if (nativeKdc != null) { 1604 System.out.println("Killing kdc..."); 1605 kdcProc.destroyForcibly(); 1606 System.out.println("Done"); 1607 } else { 1608 try { 1609 thread1.stop(); 1610 thread2.stop(); 1611 thread3.stop(); 1612 u1.close(); 1613 t1.close(); 1614 } catch (Exception e) { 1615 // OK 1616 } 1617 } 1618 } 1619 startKDC(final String host, final String krbConfFileName, final String realm, final Map<String, String> principals, final String ktab, final KtabMode mode)1620 public static KDC startKDC(final String host, final String krbConfFileName, 1621 final String realm, final Map<String, String> principals, 1622 final String ktab, final KtabMode mode) { 1623 1624 KDC kdc; 1625 try { 1626 kdc = KDC.create(realm, host, 0, true); 1627 kdc.setOption(KDC.Option.PREAUTH_REQUIRED, Boolean.FALSE); 1628 if (krbConfFileName != null) { 1629 KDC.saveConfig(krbConfFileName, kdc); 1630 } 1631 1632 // Add principals 1633 if (principals != null) { 1634 principals.forEach((name, password) -> { 1635 if (password == null || password.isEmpty()) { 1636 System.out.println(String.format( 1637 "KDC:add a principal '%s' with a random " + 1638 "password", name)); 1639 kdc.addPrincipalRandKey(name); 1640 } else { 1641 System.out.println(String.format( 1642 "KDC:add a principal '%s' with '%s' password", 1643 name, password)); 1644 kdc.addPrincipal(name, password.toCharArray()); 1645 } 1646 }); 1647 } 1648 1649 // Create or append keys to existing keytab file 1650 if (ktab != null) { 1651 File ktabFile = new File(ktab); 1652 switch(mode) { 1653 case APPEND: 1654 if (ktabFile.exists()) { 1655 System.out.println(String.format( 1656 "KDC:append keys to an exising keytab " 1657 + "file %s", ktab)); 1658 kdc.appendKtab(ktab); 1659 } else { 1660 System.out.println(String.format( 1661 "KDC:create a new keytab file %s", ktab)); 1662 kdc.writeKtab(ktab); 1663 } 1664 break; 1665 case EXISTING: 1666 System.out.println(String.format( 1667 "KDC:use an existing keytab file %s", ktab)); 1668 break; 1669 default: 1670 throw new RuntimeException(String.format( 1671 "KDC:unsupported keytab mode: %s", mode)); 1672 } 1673 } 1674 1675 System.out.println(String.format( 1676 "KDC: started on %s:%s with '%s' realm", 1677 host, kdc.getPort(), realm)); 1678 } catch (Exception e) { 1679 throw new RuntimeException("KDC: unexpected exception", e); 1680 } 1681 1682 return kdc; 1683 } 1684 1685 /** 1686 * Helper class to encapsulate a job in a KDC. 1687 */ 1688 private static class Job { 1689 byte[] token; // The received request at creation time and 1690 // the response at send time 1691 Socket s; // The TCP socket from where the request comes 1692 DataOutputStream out; // The OutputStream of the TCP socket 1693 DatagramSocket s2; // The UDP socket from where the request comes 1694 DatagramPacket dp; // The incoming UDP datagram packet 1695 boolean useTCP; // Whether TCP or UDP is used 1696 1697 // Creates a job object for TCP Job(byte[] token, Socket s, DataOutputStream out)1698 Job(byte[] token, Socket s, DataOutputStream out) { 1699 useTCP = true; 1700 this.token = token; 1701 this.s = s; 1702 this.out = out; 1703 } 1704 1705 // Creates a job object for UDP Job(byte[] token, DatagramSocket s2, DatagramPacket dp)1706 Job(byte[] token, DatagramSocket s2, DatagramPacket dp) { 1707 useTCP = false; 1708 this.token = token; 1709 this.s2 = s2; 1710 this.dp = dp; 1711 } 1712 1713 // Sends the output back to the client send()1714 void send() { 1715 try { 1716 if (useTCP) { 1717 System.out.println(">>>>> TCP request honored"); 1718 out.writeInt(token.length); 1719 out.write(token); 1720 s.close(); 1721 } else { 1722 System.out.println(">>>>> UDP request honored"); 1723 s2.send(new DatagramPacket(token, token.length, dp.getAddress(), dp.getPort())); 1724 } 1725 } catch (Exception e) { 1726 e.printStackTrace(); 1727 } 1728 } 1729 } 1730 1731 /** 1732 * A native KDC using the binaries in nativePath. Attention: 1733 * this is using binaries, not an existing KDC instance. 1734 * An implementation of this takes care of configuration, 1735 * principal db managing and KDC startup. 1736 */ 1737 static abstract class NativeKdc { 1738 1739 protected Map<String,String> env; 1740 protected String nativePath; 1741 protected String base; 1742 protected String realm; 1743 protected int port; 1744 NativeKdc(String nativePath, KDC kdc)1745 NativeKdc(String nativePath, KDC kdc) { 1746 if (kdc.port == 0) { 1747 kdc.port = 8000 + new java.util.Random().nextInt(10000); 1748 } 1749 this.nativePath = nativePath; 1750 this.realm = kdc.realm; 1751 this.port = kdc.port; 1752 this.base = Paths.get("" + port).toAbsolutePath().toString(); 1753 } 1754 1755 // Add a new principal addPrincipal(String user, String pass)1756 abstract void addPrincipal(String user, String pass); 1757 // Add a keytab entry ktadd(String user, String ktab)1758 abstract void ktadd(String user, String ktab); 1759 // Initialize KDC init()1760 abstract void init(); 1761 // Start kdc kdc()1762 abstract Process kdc(); 1763 // Configuration prepare()1764 abstract void prepare(); 1765 // Fill ccache kinit(String user, String ccache)1766 abstract void kinit(String user, String ccache); 1767 get(KDC kdc)1768 static NativeKdc get(KDC kdc) { 1769 String prop = System.getProperty("native.kdc.path"); 1770 if (prop == null) { 1771 return null; 1772 } else if (Files.exists(Paths.get(prop, "sbin/krb5kdc"))) { 1773 return new MIT(true, prop, kdc); 1774 } else if (Files.exists(Paths.get(prop, "kdc/krb5kdc"))) { 1775 return new MIT(false, prop, kdc); 1776 } else if (Files.exists(Paths.get(prop, "libexec/kdc"))) { 1777 return new Heimdal(prop, kdc); 1778 } else { 1779 throw new IllegalArgumentException("Strange " + prop); 1780 } 1781 } 1782 run(boolean wait, String... cmd)1783 Process run(boolean wait, String... cmd) { 1784 try { 1785 System.out.println("Running " + cmd2str(env, cmd)); 1786 ProcessBuilder pb = new ProcessBuilder(); 1787 pb.inheritIO(); 1788 pb.environment().putAll(env); 1789 Process p = pb.command(cmd).start(); 1790 if (wait) { 1791 if (p.waitFor() < 0) { 1792 throw new RuntimeException("exit code is not null"); 1793 } 1794 return null; 1795 } else { 1796 return p; 1797 } 1798 } catch (Exception e) { 1799 throw new RuntimeException(e); 1800 } 1801 } 1802 cmd2str(Map<String,String> env, String... cmd)1803 private String cmd2str(Map<String,String> env, String... cmd) { 1804 return env.entrySet().stream().map(e -> e.getKey()+"="+e.getValue()) 1805 .collect(Collectors.joining(" ")) + " " + 1806 Stream.of(cmd).collect(Collectors.joining(" ")); 1807 } 1808 } 1809 1810 // Heimdal KDC. Build your own and run "make install" to nativePath. 1811 static class Heimdal extends NativeKdc { 1812 Heimdal(String nativePath, KDC kdc)1813 Heimdal(String nativePath, KDC kdc) { 1814 super(nativePath, kdc); 1815 this.env = Map.of( 1816 "KRB5_CONFIG", base + "/krb5.conf", 1817 "KRB5_TRACE", "/dev/stderr", 1818 Platform.sharedLibraryPathVariableName(), nativePath + "/lib"); 1819 } 1820 1821 @Override addPrincipal(String user, String pass)1822 public void addPrincipal(String user, String pass) { 1823 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1824 "add", "-p", pass, "--use-defaults", user); 1825 } 1826 1827 @Override ktadd(String user, String ktab)1828 public void ktadd(String user, String ktab) { 1829 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1830 "ext_keytab", "-k", ktab, user); 1831 } 1832 1833 @Override init()1834 public void init() { 1835 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1836 "init", "--realm-max-ticket-life=1day", 1837 "--realm-max-renewable-life=1month", realm); 1838 } 1839 1840 @Override kdc()1841 public Process kdc() { 1842 return run(false, nativePath + "/libexec/kdc", 1843 "--addresses=127.0.0.1", "-P", "" + port); 1844 } 1845 1846 @Override prepare()1847 public void prepare() { 1848 try { 1849 Files.createDirectory(Paths.get(base)); 1850 Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList( 1851 "[libdefaults]", 1852 "default_realm = " + realm, 1853 "default_keytab_name = FILE:" + base + "/krb5.keytab", 1854 "forwardable = true", 1855 "dns_lookup_kdc = no", 1856 "dns_lookup_realm = no", 1857 "dns_canonicalize_hostname = false", 1858 "\n[realms]", 1859 realm + " = {", 1860 " kdc = localhost:" + port, 1861 "}", 1862 "\n[kdc]", 1863 "db-dir = " + base, 1864 "database = {", 1865 " label = {", 1866 " dbname = " + base + "/current-db", 1867 " realm = " + realm, 1868 " mkey_file = " + base + "/mkey.file", 1869 " acl_file = " + base + "/heimdal.acl", 1870 " log_file = " + base + "/current.log", 1871 " }", 1872 "}", 1873 SUPPORTED_ETYPES == null ? "" 1874 : ("\n[kadmin]\ndefault_keys = " 1875 + (SUPPORTED_ETYPES + ",") 1876 .replaceAll(",", ":pw-salt ")), 1877 "\n[logging]", 1878 "kdc = 0-/FILE:" + base + "/messages.log", 1879 "krb5 = 0-/FILE:" + base + "/messages.log", 1880 "default = 0-/FILE:" + base + "/messages.log" 1881 )); 1882 } catch (IOException e) { 1883 throw new UncheckedIOException(e); 1884 } 1885 } 1886 1887 @Override kinit(String user, String ccache)1888 void kinit(String user, String ccache) { 1889 String tmpName = base + "/" + user + "." + 1890 System.identityHashCode(this) + ".keytab"; 1891 ktadd(user, tmpName); 1892 run(true, nativePath + "/bin/kinit", 1893 "-f", "-t", tmpName, "-c", ccache, user); 1894 } 1895 } 1896 1897 // MIT krb5 KDC. Make your own exploded (install == false), or 1898 // "make install" into nativePath (install == true). 1899 static class MIT extends NativeKdc { 1900 1901 private boolean install; // "make install" or "make" 1902 MIT(boolean install, String nativePath, KDC kdc)1903 MIT(boolean install, String nativePath, KDC kdc) { 1904 super(nativePath, kdc); 1905 this.install = install; 1906 this.env = Map.of( 1907 "KRB5_KDC_PROFILE", base + "/kdc.conf", 1908 "KRB5_CONFIG", base + "/krb5.conf", 1909 "KRB5_TRACE", "/dev/stderr", 1910 Platform.sharedLibraryPathVariableName(), nativePath + "/lib"); 1911 } 1912 1913 @Override addPrincipal(String user, String pass)1914 public void addPrincipal(String user, String pass) { 1915 run(true, nativePath + 1916 (install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local", 1917 "-q", "addprinc -pw " + pass + " " + user); 1918 } 1919 1920 @Override ktadd(String user, String ktab)1921 public void ktadd(String user, String ktab) { 1922 run(true, nativePath + 1923 (install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local", 1924 "-q", "ktadd -k " + ktab + " -norandkey " + user); 1925 } 1926 1927 @Override init()1928 public void init() { 1929 run(true, nativePath + 1930 (install ? "/sbin/" : "/kadmin/dbutil/") + "kdb5_util", 1931 "create", "-s", "-W", "-P", "olala"); 1932 } 1933 1934 @Override kdc()1935 public Process kdc() { 1936 return run(false, nativePath + 1937 (install ? "/sbin/" : "/kdc/") + "krb5kdc", 1938 "-n"); 1939 } 1940 1941 @Override prepare()1942 public void prepare() { 1943 try { 1944 Files.createDirectory(Paths.get(base)); 1945 Files.write(Paths.get(base + "/kdc.conf"), Arrays.asList( 1946 "[kdcdefaults]", 1947 "\n[realms]", 1948 realm + "= {", 1949 " kdc_listen = " + this.port, 1950 " kdc_tcp_listen = " + this.port, 1951 " database_name = " + base + "/principal", 1952 " key_stash_file = " + base + "/.k5.ATHENA.MIT.EDU", 1953 SUPPORTED_ETYPES == null ? "" 1954 : (" supported_enctypes = " 1955 + (SUPPORTED_ETYPES + ",") 1956 .replaceAll(",", ":normal ")), 1957 "}" 1958 )); 1959 Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList( 1960 "[libdefaults]", 1961 "default_realm = " + realm, 1962 "default_keytab_name = FILE:" + base + "/krb5.keytab", 1963 "forwardable = true", 1964 "dns_lookup_kdc = no", 1965 "dns_lookup_realm = no", 1966 "dns_canonicalize_hostname = false", 1967 "\n[realms]", 1968 realm + " = {", 1969 " kdc = localhost:" + port, 1970 "}", 1971 "\n[logging]", 1972 "kdc = FILE:" + base + "/krb5kdc.log" 1973 )); 1974 } catch (IOException e) { 1975 throw new UncheckedIOException(e); 1976 } 1977 } 1978 1979 @Override kinit(String user, String ccache)1980 void kinit(String user, String ccache) { 1981 String tmpName = base + "/" + user + "." + 1982 System.identityHashCode(this) + ".keytab"; 1983 ktadd(user, tmpName); 1984 run(true, nativePath + 1985 (install ? "/bin/" : "/clients/kinit/") + "kinit", 1986 "-f", "-t", tmpName, "-c", ccache, user); 1987 } 1988 } 1989 1990 // Calling private methods thru reflections 1991 private static final Field getEType; 1992 private static final Constructor<EncryptedData> ctorEncryptedData; 1993 private static final Method stringToKey; 1994 private static final Field getAddlTkt; 1995 1996 static { 1997 try { 1998 ctorEncryptedData = EncryptedData.class.getDeclaredConstructor(DerValue.class); 1999 ctorEncryptedData.setAccessible(true); 2000 getEType = KDCReqBody.class.getDeclaredField("eType"); 2001 getEType.setAccessible(true); 2002 stringToKey = EncryptionKey.class.getDeclaredMethod( 2003 "stringToKey", 2004 char[].class, String.class, byte[].class, Integer.TYPE); 2005 stringToKey.setAccessible(true); 2006 getAddlTkt = KDCReqBody.class.getDeclaredField("additionalTickets"); 2007 getAddlTkt.setAccessible(true); 2008 } catch (NoSuchFieldException nsfe) { 2009 throw new AssertionError(nsfe); 2010 } catch (NoSuchMethodException nsme) { 2011 throw new AssertionError(nsme); 2012 } 2013 } newEncryptedData(DerValue der)2014 private EncryptedData newEncryptedData(DerValue der) { 2015 try { 2016 return ctorEncryptedData.newInstance(der); 2017 } catch (Exception e) { 2018 throw new AssertionError(e); 2019 } 2020 } KDCReqBodyDotEType(KDCReqBody body)2021 private static int[] KDCReqBodyDotEType(KDCReqBody body) { 2022 try { 2023 return (int[]) getEType.get(body); 2024 } catch (Exception e) { 2025 throw new AssertionError(e); 2026 } 2027 } EncryptionKeyDotStringToKey(char[] password, String salt, byte[] s2kparams, int keyType)2028 private static byte[] EncryptionKeyDotStringToKey(char[] password, String salt, 2029 byte[] s2kparams, int keyType) throws KrbCryptoException { 2030 try { 2031 return (byte[])stringToKey.invoke( 2032 null, password, salt, s2kparams, keyType); 2033 } catch (InvocationTargetException ex) { 2034 throw (KrbCryptoException)ex.getCause(); 2035 } catch (Exception e) { 2036 throw new AssertionError(e); 2037 } 2038 } KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body)2039 private static Ticket KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body) { 2040 try { 2041 return ((Ticket[])getAddlTkt.get(body))[0]; 2042 } catch (Exception e) { 2043 throw new AssertionError(e); 2044 } 2045 } 2046 } 2047