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