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 java.lang.reflect.Constructor; 25 import java.lang.reflect.Field; 26 import java.lang.reflect.InvocationTargetException; 27 import java.net.*; 28 import java.io.*; 29 import java.lang.reflect.Method; 30 import java.nio.file.Files; 31 import java.nio.file.Paths; 32 import java.util.*; 33 import java.util.concurrent.*; 34 import java.util.stream.Collectors; 35 import java.util.stream.Stream; 36 37 import sun.security.krb5.*; 38 import sun.security.krb5.internal.*; 39 import sun.security.krb5.internal.ccache.CredentialsCache; 40 import sun.security.krb5.internal.crypto.EType; 41 import sun.security.krb5.internal.crypto.KeyUsage; 42 import sun.security.krb5.internal.ktab.KeyTab; 43 import sun.security.util.DerInputStream; 44 import sun.security.util.DerOutputStream; 45 import sun.security.util.DerValue; 46 47 /** 48 * A KDC server. 49 * 50 * Note: By setting the system property native.kdc.path to a native 51 * krb5 installation, this class starts a native KDC with the 52 * given realm and host. It can also add new principals and save keytabs. 53 * Other features might not be available. 54 * <p> 55 * Features: 56 * <ol> 57 * <li> Supports TCP and UDP 58 * <li> Supports AS-REQ and TGS-REQ 59 * <li> Principal db and other settings hard coded in application 60 * <li> Options, say, request preauth or not 61 * </ol> 62 * Side effects: 63 * <ol> 64 * <li> The Sun-internal class <code>sun.security.krb5.Config</code> is a 65 * singleton and initialized according to Kerberos settings (krb5.conf and 66 * java.security.krb5.* system properties). This means once it's initialized 67 * it will not automatically notice any changes to these settings (or file 68 * changes of krb5.conf). The KDC class normally does not touch these 69 * settings (except for the <code>writeKtab()</code> method). However, to make 70 * sure nothing ever goes wrong, if you want to make any changes to these 71 * settings after calling a KDC method, call <code>Config.refresh()</code> to 72 * make sure your changes are reflected in the <code>Config</code> object. 73 * </ol> 74 * System properties recognized: 75 * <ul> 76 * <li>test.kdc.save.ccache 77 * </ul> 78 * Issues and TODOs: 79 * <ol> 80 * <li> Generates krb5.conf to be used on another machine, currently the kdc is 81 * always localhost 82 * <li> More options to KDC, say, error output, say, response nonce != 83 * request nonce 84 * </ol> 85 * Note: This program uses internal krb5 classes (including reflection to 86 * access private fields and methods). 87 * <p> 88 * Usages: 89 * <p> 90 * 1. Init and start the KDC: 91 * <pre> 92 * KDC kdc = KDC.create("REALM.NAME", port, isDaemon); 93 * KDC kdc = KDC.create("REALM.NAME"); 94 * </pre> 95 * Here, <code>port</code> is the UDP and TCP port number the KDC server 96 * listens on. If zero, a random port is chosen, which you can use getPort() 97 * later to retrieve the value. 98 * <p> 99 * If <code>isDaemon</code> is true, the KDC worker threads will be daemons. 100 * <p> 101 * The shortcut <code>KDC.create("REALM.NAME")</code> has port=0 and 102 * isDaemon=false, and is commonly used in an embedded KDC. 103 * <p> 104 * 2. Adding users: 105 * <pre> 106 * kdc.addPrincipal(String principal_name, char[] password); 107 * kdc.addPrincipalRandKey(String principal_name); 108 * </pre> 109 * A service principal's name should look like "host/f.q.d.n". The second form 110 * generates a random key. To expose this key, call <code>writeKtab()</code> to 111 * save the keys into a keytab file. 112 * <p> 113 * Note that you need to add the principal name krbtgt/REALM.NAME yourself. 114 * <p> 115 * Note that you can safely add a principal at any time after the KDC is 116 * started and before a user requests info on this principal. 117 * <p> 118 * 3. Other public methods: 119 * <ul> 120 * <li> <code>getPort</code>: Returns the port number the KDC uses 121 * <li> <code>getRealm</code>: Returns the realm name 122 * <li> <code>writeKtab</code>: Writes all principals' keys into a keytab file 123 * <li> <code>saveConfig</code>: Saves a krb5.conf file to access this KDC 124 * <li> <code>setOption</code>: Sets various options 125 * </ul> 126 * Read the javadoc for details. Lazy developer can use <code>OneKDC</code> 127 * directly. 128 */ 129 public class KDC { 130 131 public static final int DEFAULT_LIFETIME = 39600; 132 public static final int DEFAULT_RENEWTIME = 86400; 133 134 public static final String NOT_EXISTING_HOST = "not.existing.host"; 135 136 // What etypes the KDC supports. Comma-separated strings. Null for all. 137 // Please note native KDCs might use different names. 138 private static final String SUPPORTED_ETYPES 139 = System.getProperty("kdc.supported.enctypes"); 140 141 // The native KDC 142 private final NativeKdc nativeKdc; 143 144 // The native KDC process 145 private Process kdcProc = null; 146 147 // Under the hood. 148 149 // Principal db. principal -> pass. A case-insensitive TreeMap is used 150 // so that even if the client provides a name with different case, the KDC 151 // can still locate the principal and give back correct salt. 152 private TreeMap<String,char[]> passwords = new TreeMap<> 153 (String.CASE_INSENSITIVE_ORDER); 154 155 // Non default salts. Precisely, there should be different salts for 156 // different etypes, pretend they are the same at the moment. 157 private TreeMap<String,String> salts = new TreeMap<> 158 (String.CASE_INSENSITIVE_ORDER); 159 160 // Non default s2kparams for newer etypes. Precisely, there should be 161 // different s2kparams for different etypes, pretend they are the same 162 // at the moment. 163 private TreeMap<String,byte[]> s2kparamses = new TreeMap<> 164 (String.CASE_INSENSITIVE_ORDER); 165 166 // Alias for referrals. 167 private TreeMap<String,KDC> aliasReferrals = new TreeMap<> 168 (String.CASE_INSENSITIVE_ORDER); 169 170 // Alias for local resolution. 171 private TreeMap<String,PrincipalName> alias2Principals = new TreeMap<> 172 (String.CASE_INSENSITIVE_ORDER); 173 174 // Realm name 175 private String realm; 176 // KDC 177 private String kdc; 178 // Service port number 179 private int port; 180 // The request/response job queue 181 private BlockingQueue<Job> q = new ArrayBlockingQueue<>(100); 182 // Options 183 private Map<Option,Object> options = new HashMap<>(); 184 // Realm-specific krb5.conf settings 185 private List<String> conf = new ArrayList<>(); 186 187 private Thread thread1, thread2, thread3; 188 private volatile boolean udpConsumerReady = false; 189 private volatile boolean tcpConsumerReady = false; 190 private volatile boolean dispatcherReady = false; 191 DatagramSocket u1 = null; 192 ServerSocket t1 = null; 193 194 public static enum KtabMode { APPEND, EXISTING }; 195 196 /** 197 * Option names, to be expanded forever. 198 */ 199 public static enum Option { 200 /** 201 * Whether pre-authentication is required. Default Boolean.TRUE 202 */ 203 PREAUTH_REQUIRED, 204 /** 205 * Only issue TGT in RC4 206 */ 207 ONLY_RC4_TGT, 208 /** 209 * Use RC4 as the first in preauth 210 */ 211 RC4_FIRST_PREAUTH, 212 /** 213 * Use only one preauth, so that some keys are not easy to generate 214 */ 215 ONLY_ONE_PREAUTH, 216 /** 217 * Set all name-type to a value in response 218 */ 219 RESP_NT, 220 /** 221 * Multiple ETYPE-INFO-ENTRY with same etype but different salt 222 */ 223 DUP_ETYPE, 224 /** 225 * What backend server can be delegated to 226 */ 227 OK_AS_DELEGATE, 228 /** 229 * Allow S4U2self, List<String> of middle servers. 230 * If not set, means KDC does not understand S4U2self at all, therefore 231 * would ignore any PA-FOR-USER request and send a ticket using the 232 * cname of teh requestor. If set, it returns FORWARDABLE tickets to 233 * a server with its name in the list 234 */ 235 ALLOW_S4U2SELF, 236 /** 237 * Allow S4U2proxy, Map<String,List<String>> of middle servers to 238 * backends. If not set or a backend not in a server's list, 239 * Krb5.KDC_ERR_POLICY will be send for S4U2proxy request. 240 */ 241 ALLOW_S4U2PROXY, 242 /** 243 * Sensitive accounts can never be delegated. 244 */ 245 SENSITIVE_ACCOUNTS, 246 /** 247 * If true, will check if TGS-REQ contains a non-null addresses field. 248 */ 249 CHECK_ADDRESSES, 250 }; 251 252 /** 253 * A standalone KDC server. 254 */ main(String[] args)255 public static void main(String[] args) throws Exception { 256 int port = args.length > 0 ? Integer.parseInt(args[0]) : 0; 257 KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", port, false); 258 kdc.addPrincipal("dummy", "bogus".toCharArray()); 259 kdc.addPrincipal("foo", "bar".toCharArray()); 260 kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE"); 261 kdc.addPrincipalRandKey("server/host.rabbit.hole"); 262 kdc.addPrincipalRandKey("backend/host.rabbit.hole"); 263 KDC.saveConfig("krb5.conf", kdc, "forwardable = true"); 264 } 265 266 /** 267 * Creates and starts a KDC running as a daemon on a random port. 268 * @param realm the realm name 269 * @return the running KDC instance 270 * @throws java.io.IOException for any socket creation error 271 */ create(String realm)272 public static KDC create(String realm) throws IOException { 273 return create(realm, "kdc." + realm.toLowerCase(Locale.US), 0, true); 274 } 275 existing(String realm, String kdc, int port)276 public static KDC existing(String realm, String kdc, int port) { 277 KDC k = new KDC(realm, kdc); 278 k.port = port; 279 return k; 280 } 281 282 /** 283 * Creates and starts a KDC server. 284 * @param realm the realm name 285 * @param port the TCP and UDP port to listen to. A random port will to 286 * chosen if zero. 287 * @param asDaemon if true, KDC threads will be daemons. Otherwise, not. 288 * @return the running KDC instance 289 * @throws java.io.IOException for any socket creation error 290 */ create(String realm, String kdc, int port, boolean asDaemon)291 public static KDC create(String realm, String kdc, int port, 292 boolean asDaemon) throws IOException { 293 return new KDC(realm, kdc, port, asDaemon); 294 } 295 296 /** 297 * Sets an option 298 * @param key the option name 299 * @param value the value 300 */ setOption(Option key, Object value)301 public void setOption(Option key, Object value) { 302 if (value == null) { 303 options.remove(key); 304 } else { 305 options.put(key, value); 306 } 307 } 308 309 /** 310 * Writes or appends keys into a keytab. 311 * <p> 312 * Attention: This is the most basic one of a series of methods below on 313 * keytab creation or modification. All these methods reference krb5.conf 314 * settings. If you need to modify krb5.conf or switch to another krb5.conf 315 * later, please call <code>Config.refresh()</code> again. For example: 316 * <pre> 317 * kdc.writeKtab("/etc/kdc/ktab", true); // Config is initialized, 318 * System.setProperty("java.security.krb5.conf", "/home/mykrb5.conf"); 319 * Config.refresh(); 320 * </pre> 321 * Inside this method there are 2 places krb5.conf is used: 322 * <ol> 323 * <li> (Fatal) Generating keys: EncryptionKey.acquireSecretKeys 324 * <li> (Has workaround) Creating PrincipalName 325 * </ol> 326 * @param tab the keytab file name 327 * @param append true if append, otherwise, overwrite. 328 * @param names the names to write into, write all if names is empty 329 */ writeKtab(String tab, boolean append, String... names)330 public void writeKtab(String tab, boolean append, String... names) 331 throws IOException, KrbException { 332 KeyTab ktab = null; 333 if (nativeKdc == null) { 334 ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab); 335 } 336 Iterable<String> entries = 337 (names.length != 0) ? Arrays.asList(names): passwords.keySet(); 338 for (String name : entries) { 339 if (name.indexOf('@') < 0) { 340 name = name + "@" + realm; 341 } 342 if (nativeKdc == null) { 343 char[] pass = passwords.get(name); 344 int kvno = 0; 345 if (Character.isDigit(pass[pass.length - 1])) { 346 kvno = pass[pass.length - 1] - '0'; 347 } 348 PrincipalName pn = new PrincipalName(name, 349 name.indexOf('/') < 0 ? 350 PrincipalName.KRB_NT_UNKNOWN : 351 PrincipalName.KRB_NT_SRV_HST); 352 ktab.addEntry(pn, 353 getSalt(pn), 354 pass, 355 kvno, 356 true); 357 } else { 358 nativeKdc.ktadd(name, tab); 359 } 360 } 361 if (nativeKdc == null) { 362 ktab.save(); 363 } 364 } 365 366 /** 367 * Writes all principals' keys from multiple KDCs into one keytab file. 368 * @throws java.io.IOException for any file output error 369 * @throws sun.security.krb5.KrbException for any realm and/or principal 370 * name error. 371 */ writeMultiKtab(String tab, KDC... kdcs)372 public static void writeMultiKtab(String tab, KDC... kdcs) 373 throws IOException, KrbException { 374 KeyTab.create(tab).save(); // Empty the old keytab 375 appendMultiKtab(tab, kdcs); 376 } 377 378 /** 379 * Appends all principals' keys from multiple KDCs to one keytab file. 380 */ appendMultiKtab(String tab, KDC... kdcs)381 public static void appendMultiKtab(String tab, KDC... kdcs) 382 throws IOException, KrbException { 383 for (KDC kdc: kdcs) { 384 kdc.writeKtab(tab, true); 385 } 386 } 387 388 /** 389 * Write a ktab for this KDC. 390 */ writeKtab(String tab)391 public void writeKtab(String tab) throws IOException, KrbException { 392 writeKtab(tab, false); 393 } 394 395 /** 396 * Appends keys in this KDC to a ktab. 397 */ appendKtab(String tab)398 public void appendKtab(String tab) throws IOException, KrbException { 399 writeKtab(tab, true); 400 } 401 402 /** 403 * Adds a new principal to this realm with a given password. 404 * @param user the principal's name. For a service principal, use the 405 * form of host/f.q.d.n 406 * @param pass the password for the principal 407 */ addPrincipal(String user, char[] pass)408 public void addPrincipal(String user, char[] pass) { 409 addPrincipal(user, pass, null, null); 410 } 411 412 /** 413 * Adds a new principal to this realm with a given password. 414 * @param user the principal's name. For a service principal, use the 415 * form of host/f.q.d.n 416 * @param pass the password for the principal 417 * @param salt the salt, or null if a default value will be used 418 * @param s2kparams the s2kparams, or null if a default value will be used 419 */ addPrincipal( String user, char[] pass, String salt, byte[] s2kparams)420 public void addPrincipal( 421 String user, char[] pass, String salt, byte[] s2kparams) { 422 if (user.indexOf('@') < 0) { 423 user = user + "@" + realm; 424 } 425 if (nativeKdc != null) { 426 if (!user.equals("krbtgt/" + realm)) { 427 nativeKdc.addPrincipal(user, new String(pass)); 428 } 429 passwords.put(user, new char[0]); 430 } else { 431 passwords.put(user, pass); 432 if (salt != null) { 433 salts.put(user, salt); 434 } 435 if (s2kparams != null) { 436 s2kparamses.put(user, s2kparams); 437 } 438 } 439 } 440 441 /** 442 * Adds a new principal to this realm with a random password 443 * @param user the principal's name. For a service principal, use the 444 * form of host/f.q.d.n 445 */ addPrincipalRandKey(String user)446 public void addPrincipalRandKey(String user) { 447 addPrincipal(user, randomPassword()); 448 } 449 450 /** 451 * Returns the name of this realm 452 * @return the name of this realm 453 */ getRealm()454 public String getRealm() { 455 return realm; 456 } 457 458 /** 459 * Returns the name of kdc 460 * @return the name of kdc 461 */ getKDC()462 public String getKDC() { 463 return kdc; 464 } 465 466 /** 467 * Add realm-specific krb5.conf setting 468 */ addConf(String s)469 public void addConf(String s) { 470 conf.add(s); 471 } 472 473 /** 474 * Writes a krb5.conf for one or more KDC that includes KDC locations for 475 * each realm and the default realm name. You can also add extra strings 476 * into the file. The method should be called like: 477 * <pre> 478 * KDC.saveConfig("krb5.conf", kdc1, kdc2, ..., line1, line2, ...); 479 * </pre> 480 * Here you can provide one or more kdc# and zero or more line# arguments. 481 * The line# will be put after [libdefaults] and before [realms]. Therefore 482 * you can append new lines into [libdefaults] and/or create your new 483 * stanzas as well. Note that a newline character will be appended to 484 * each line# argument. 485 * <p> 486 * For example: 487 * <pre> 488 * KDC.saveConfig("krb5.conf", this); 489 * </pre> 490 * generates: 491 * <pre> 492 * [libdefaults] 493 * default_realm = REALM.NAME 494 * 495 * [realms] 496 * REALM.NAME = { 497 * kdc = host:port_number 498 * # realm-specific settings 499 * } 500 * </pre> 501 * 502 * Another example: 503 * <pre> 504 * KDC.saveConfig("krb5.conf", kdc1, kdc2, "forwardable = true", "", 505 * "[domain_realm]", 506 * ".kdc1.com = KDC1.NAME"); 507 * </pre> 508 * generates: 509 * <pre> 510 * [libdefaults] 511 * default_realm = KDC1.NAME 512 * forwardable = true 513 * 514 * [domain_realm] 515 * .kdc1.com = KDC1.NAME 516 * 517 * [realms] 518 * KDC1.NAME = { 519 * kdc = host:port1 520 * } 521 * KDC2.NAME = { 522 * kdc = host:port2 523 * } 524 * </pre> 525 * @param file the name of the file to write into 526 * @param kdc the first (and default) KDC 527 * @param more more KDCs or extra lines (in their appearing order) to 528 * insert into the krb5.conf file. This method reads each argument's type 529 * to determine what it's for. This argument can be empty. 530 * @throws java.io.IOException for any file output error 531 */ saveConfig(String file, KDC kdc, Object... more)532 public static void saveConfig(String file, KDC kdc, Object... more) 533 throws IOException { 534 StringBuffer sb = new StringBuffer(); 535 sb.append("[libdefaults]\ndefault_realm = "); 536 sb.append(kdc.realm); 537 sb.append("\n"); 538 for (Object o : more) { 539 if (o instanceof String) { 540 sb.append(o); 541 sb.append("\n"); 542 } 543 } 544 sb.append("\n[realms]\n"); 545 sb.append(kdc.realmLine()); 546 for (Object o : more) { 547 if (o instanceof KDC) { 548 sb.append(((KDC) o).realmLine()); 549 } 550 } 551 Files.write(Paths.get(file), sb.toString().getBytes()); 552 } 553 554 /** 555 * Returns the service port of the KDC server. 556 * @return the KDC service port 557 */ getPort()558 public int getPort() { 559 return port; 560 } 561 562 /** 563 * Register an alias name to be referred to a different KDC for 564 * resolution, according to RFC 6806. 565 * @param alias Alias name (i.e. user@REALM.COM). 566 * @param referredKDC KDC to which the alias is referred for resolution. 567 */ registerAlias(String alias, KDC referredKDC)568 public void registerAlias(String alias, KDC referredKDC) { 569 aliasReferrals.remove(alias); 570 aliasReferrals.put(alias, referredKDC); 571 } 572 573 /** 574 * Register an alias to be resolved to a Principal Name locally, 575 * according to RFC 6806. 576 * @param alias Alias name (i.e. user@REALM.COM). 577 * @param user Principal Name to which the alias is resolved. 578 */ registerAlias(String alias, String user)579 public void registerAlias(String alias, String user) 580 throws RealmException { 581 alias2Principals.remove(alias); 582 alias2Principals.put(alias, new PrincipalName(user)); 583 } 584 585 // Private helper methods 586 587 /** 588 * Private constructor, cannot be called outside. 589 * @param realm 590 */ KDC(String realm, String kdc)591 private KDC(String realm, String kdc) { 592 this.realm = realm; 593 this.kdc = kdc; 594 this.nativeKdc = null; 595 } 596 597 /** 598 * A constructor that starts the KDC service also. 599 */ KDC(String realm, String kdc, int port, boolean asDaemon)600 protected KDC(String realm, String kdc, int port, boolean asDaemon) 601 throws IOException { 602 this.realm = realm; 603 this.kdc = kdc; 604 this.nativeKdc = NativeKdc.get(this); 605 startServer(port, asDaemon); 606 } 607 /** 608 * Generates a 32-char random password 609 * @return the password 610 */ randomPassword()611 private static char[] randomPassword() { 612 char[] pass = new char[32]; 613 Random r = new Random(); 614 for (int i=0; i<31; i++) 615 pass[i] = (char)('a' + r.nextInt(26)); 616 // The last char cannot be a number, otherwise, keyForUser() 617 // believes it's a sign of kvno 618 pass[31] = 'Z'; 619 return pass; 620 } 621 622 /** 623 * Generates a random key for the given encryption type. 624 * @param eType the encryption type 625 * @return the generated key 626 * @throws sun.security.krb5.KrbException for unknown/unsupported etype 627 */ generateRandomKey(int eType)628 private static EncryptionKey generateRandomKey(int eType) 629 throws KrbException { 630 return genKey0(randomPassword(), "NOTHING", null, eType, null); 631 } 632 633 /** 634 * Returns the password for a given principal 635 * @param p principal 636 * @return the password 637 * @throws sun.security.krb5.KrbException when the principal is not inside 638 * the database. 639 */ getPassword(PrincipalName p, boolean server)640 private char[] getPassword(PrincipalName p, boolean server) 641 throws KrbException { 642 String pn = p.toString(); 643 if (p.getRealmString() == null) { 644 pn = pn + "@" + getRealm(); 645 } 646 char[] pass = passwords.get(pn); 647 if (pass == null) { 648 throw new KrbException(server? 649 Krb5.KDC_ERR_S_PRINCIPAL_UNKNOWN: 650 Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN, pn.toString()); 651 } 652 return pass; 653 } 654 655 /** 656 * Returns the salt string for the principal. 657 * @param p principal 658 * @return the salt 659 */ getSalt(PrincipalName p)660 protected String getSalt(PrincipalName p) { 661 String pn = p.toString(); 662 if (p.getRealmString() == null) { 663 pn = pn + "@" + getRealm(); 664 } 665 if (salts.containsKey(pn)) { 666 return salts.get(pn); 667 } 668 if (passwords.containsKey(pn)) { 669 try { 670 // Find the principal name with correct case. 671 p = new PrincipalName(passwords.ceilingEntry(pn).getKey()); 672 } catch (RealmException re) { 673 // Won't happen 674 } 675 } 676 String s = p.getRealmString(); 677 if (s == null) s = getRealm(); 678 for (String n: p.getNameStrings()) { 679 s += n; 680 } 681 return s; 682 } 683 684 /** 685 * Returns the s2kparams for the principal given the etype. 686 * @param p principal 687 * @param etype encryption type 688 * @return the s2kparams, might be null 689 */ getParams(PrincipalName p, int etype)690 protected byte[] getParams(PrincipalName p, int etype) { 691 switch (etype) { 692 case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: 693 case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: 694 case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128: 695 case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192: 696 String pn = p.toString(); 697 if (p.getRealmString() == null) { 698 pn = pn + "@" + getRealm(); 699 } 700 if (s2kparamses.containsKey(pn)) { 701 return s2kparamses.get(pn); 702 } 703 if (etype < EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128) { 704 return new byte[]{0, 0, 0x10, 0}; 705 } else { 706 return new byte[]{0, 0, (byte) 0x80, 0}; 707 } 708 default: 709 return null; 710 } 711 } 712 713 /** 714 * Returns the key for a given principal of the given encryption type 715 * @param p the principal 716 * @param etype the encryption type 717 * @param server looking for a server principal? 718 * @return the key 719 * @throws sun.security.krb5.KrbException for unknown/unsupported etype 720 */ keyForUser(PrincipalName p, int etype, boolean server)721 EncryptionKey keyForUser(PrincipalName p, int etype, boolean server) 722 throws KrbException { 723 try { 724 // Do not call EncryptionKey.acquireSecretKeys(), otherwise 725 // the krb5.conf config file would be loaded. 726 Integer kvno = null; 727 // For service whose password ending with a number, use it as kvno. 728 // Kvno must be postive. 729 if (p.toString().indexOf('/') > 0) { 730 char[] pass = getPassword(p, server); 731 if (Character.isDigit(pass[pass.length-1])) { 732 kvno = pass[pass.length-1] - '0'; 733 } 734 } 735 return genKey0(getPassword(p, server), getSalt(p), 736 getParams(p, etype), etype, kvno); 737 } catch (KrbException ke) { 738 throw ke; 739 } catch (Exception e) { 740 throw new RuntimeException(e); // should not happen 741 } 742 } 743 744 /** 745 * Returns a KerberosTime. 746 * 747 * @param offset offset from NOW in seconds 748 */ timeAfter(int offset)749 private static KerberosTime timeAfter(int offset) { 750 return new KerberosTime(new Date().getTime() + offset * 1000L); 751 } 752 753 /** 754 * Generates key from password. 755 */ genKey0( char[] pass, String salt, byte[] s2kparams, int etype, Integer kvno)756 private static EncryptionKey genKey0( 757 char[] pass, String salt, byte[] s2kparams, 758 int etype, Integer kvno) throws KrbException { 759 return new EncryptionKey(EncryptionKeyDotStringToKey( 760 pass, salt, s2kparams, etype), 761 etype, kvno); 762 } 763 764 /** 765 * Processes an incoming request and generates a response. 766 * @param in the request 767 * @return the response 768 * @throws java.lang.Exception for various errors 769 */ processMessage(byte[] in)770 protected byte[] processMessage(byte[] in) throws Exception { 771 if ((in[0] & 0x1f) == Krb5.KRB_AS_REQ) 772 return processAsReq(in); 773 else 774 return processTgsReq(in); 775 } 776 777 /** 778 * Processes a TGS_REQ and generates a TGS_REP (or KRB_ERROR) 779 * @param in the request 780 * @return the response 781 * @throws java.lang.Exception for various errors 782 */ processTgsReq(byte[] in)783 protected byte[] processTgsReq(byte[] in) throws Exception { 784 TGSReq tgsReq = new TGSReq(in); 785 PrincipalName service = tgsReq.reqBody.sname; 786 if (options.containsKey(KDC.Option.RESP_NT)) { 787 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT), 788 service.getNameStrings(), service.getRealm()); 789 } 790 try { 791 System.out.println(realm + "> " + tgsReq.reqBody.cname + 792 " sends TGS-REQ for " + 793 service + ", " + tgsReq.reqBody.kdcOptions); 794 KDCReqBody body = tgsReq.reqBody; 795 int[] eTypes = filterSupported(KDCReqBodyDotEType(body)); 796 if (eTypes.length == 0) { 797 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 798 } 799 int e2 = eTypes[0]; // etype for outgoing session key 800 int e3 = eTypes[0]; // etype for outgoing ticket 801 802 PAData[] pas = tgsReq.pAData; 803 804 Ticket tkt = null; 805 EncTicketPart etp = null; 806 807 PrincipalName cname = null; 808 boolean allowForwardable = true; 809 boolean isReferral = false; 810 if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) { 811 System.out.println(realm + "> verifying referral for " + 812 body.sname.getNameString()); 813 KDC referral = aliasReferrals.get(body.sname.getNameString()); 814 if (referral != null) { 815 service = new PrincipalName( 816 PrincipalName.TGS_DEFAULT_SRV_NAME + 817 PrincipalName.NAME_COMPONENT_SEPARATOR_STR + 818 referral.getRealm(), PrincipalName.KRB_NT_SRV_INST, 819 this.getRealm()); 820 System.out.println(realm + "> referral to " + 821 referral.getRealm()); 822 isReferral = true; 823 } 824 } 825 826 if (pas == null || pas.length == 0) { 827 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 828 } else { 829 PrincipalName forUserCName = null; 830 for (PAData pa: pas) { 831 if (pa.getType() == Krb5.PA_TGS_REQ) { 832 APReq apReq = new APReq(pa.getValue()); 833 tkt = apReq.ticket; 834 int te = tkt.encPart.getEType(); 835 EncryptionKey kkey = keyForUser(tkt.sname, te, true); 836 byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET); 837 DerInputStream derIn = new DerInputStream(bb); 838 DerValue der = derIn.getDerValue(); 839 etp = new EncTicketPart(der.toByteArray()); 840 // Finally, cname will be overwritten by PA-FOR-USER 841 // if it exists. 842 cname = etp.cname; 843 System.out.println(realm + "> presenting a ticket of " 844 + etp.cname + " to " + tkt.sname); 845 } else if (pa.getType() == Krb5.PA_FOR_USER) { 846 if (options.containsKey(Option.ALLOW_S4U2SELF)) { 847 PAForUserEnc p4u = new PAForUserEnc( 848 new DerValue(pa.getValue()), null); 849 forUserCName = p4u.name; 850 System.out.println(realm + "> See PA_FOR_USER " 851 + " in the name of " + p4u.name); 852 } 853 } 854 } 855 if (forUserCName != null) { 856 List<String> names = (List<String>) 857 options.get(Option.ALLOW_S4U2SELF); 858 if (!names.contains(cname.toString())) { 859 // Mimic the normal KDC behavior. When a server is not 860 // allowed to send S4U2self, do not send an error. 861 // Instead, send a ticket which is useless later. 862 allowForwardable = false; 863 } 864 cname = forUserCName; 865 } 866 if (tkt == null) { 867 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); 868 } 869 } 870 871 // Session key for original ticket, TGT 872 EncryptionKey ckey = etp.key; 873 874 // Session key for session with the service 875 EncryptionKey key = generateRandomKey(e2); 876 877 // Check time, TODO 878 KerberosTime from = body.from; 879 KerberosTime till = body.till; 880 if (from == null || from.isZero()) { 881 from = timeAfter(0); 882 } 883 if (till == null) { 884 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO 885 } else if (till.isZero()) { 886 till = timeAfter(DEFAULT_LIFETIME); 887 } 888 889 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; 890 if (body.kdcOptions.get(KDCOptions.FORWARDABLE) 891 && allowForwardable) { 892 List<String> sensitives = (List<String>) 893 options.get(Option.SENSITIVE_ACCOUNTS); 894 if (sensitives != null && sensitives.contains(cname.toString())) { 895 // Cannot make FORWARDABLE 896 } else { 897 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; 898 } 899 } 900 // We do not request for addresses for FORWARDED tickets 901 if (options.containsKey(Option.CHECK_ADDRESSES) 902 && body.kdcOptions.get(KDCOptions.FORWARDED) 903 && body.addresses != null) { 904 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 905 } 906 if (body.kdcOptions.get(KDCOptions.FORWARDED) || 907 etp.flags.get(Krb5.TKT_OPTS_FORWARDED)) { 908 bFlags[Krb5.TKT_OPTS_FORWARDED] = true; 909 } 910 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { 911 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 912 //renew = timeAfter(3600 * 24 * 7); 913 } 914 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { 915 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; 916 } 917 if (body.kdcOptions.get(KDCOptions.POSTDATED)) { 918 bFlags[Krb5.TKT_OPTS_POSTDATED] = true; 919 } 920 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { 921 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; 922 } 923 if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT)) { 924 if (!options.containsKey(Option.ALLOW_S4U2PROXY)) { 925 // Don't understand CNAME_IN_ADDL_TKT 926 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 927 } else { 928 Map<String,List<String>> map = (Map<String,List<String>>) 929 options.get(Option.ALLOW_S4U2PROXY); 930 Ticket second = KDCReqBodyDotFirstAdditionalTicket(body); 931 EncryptionKey key2 = keyForUser( 932 second.sname, second.encPart.getEType(), true); 933 byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET); 934 DerInputStream derIn = new DerInputStream(bb); 935 DerValue der = derIn.getDerValue(); 936 EncTicketPart tktEncPart = new EncTicketPart(der.toByteArray()); 937 if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)) { 938 //throw new KrbException(Krb5.KDC_ERR_BADOPTION); 939 } 940 PrincipalName client = tktEncPart.cname; 941 System.out.println(realm + "> and an additional ticket of " 942 + client + " to " + second.sname); 943 if (map.containsKey(cname.toString())) { 944 if (map.get(cname.toString()).contains(service.toString())) { 945 System.out.println(realm + "> S4U2proxy OK"); 946 } else { 947 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 948 } 949 } else { 950 throw new KrbException(Krb5.KDC_ERR_BADOPTION); 951 } 952 cname = client; 953 } 954 } 955 956 String okAsDelegate = (String)options.get(Option.OK_AS_DELEGATE); 957 if (okAsDelegate != null && ( 958 okAsDelegate.isEmpty() || 959 okAsDelegate.contains(service.getNameString()))) { 960 bFlags[Krb5.TKT_OPTS_DELEGATE] = true; 961 } 962 bFlags[Krb5.TKT_OPTS_INITIAL] = true; 963 964 KerberosTime renewTill = etp.renewTill; 965 if (renewTill != null && body.kdcOptions.get(KDCOptions.RENEW)) { 966 // till should never pass renewTill 967 if (till.greaterThan(renewTill)) { 968 till = renewTill; 969 } 970 if (System.getProperty("test.set.null.renew") != null) { 971 // Testing 8186576, see NullRenewUntil.java. 972 renewTill = null; 973 } 974 } 975 976 TicketFlags tFlags = new TicketFlags(bFlags); 977 EncTicketPart enc = new EncTicketPart( 978 tFlags, 979 key, 980 cname, 981 new TransitedEncoding(1, new byte[0]), // TODO 982 timeAfter(0), 983 from, 984 till, renewTill, 985 body.addresses != null ? body.addresses 986 : etp.caddr, 987 null); 988 EncryptionKey skey = keyForUser(service, e3, true); 989 if (skey == null) { 990 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO 991 } 992 Ticket t = new Ticket( 993 System.getProperty("test.kdc.diff.sname") != null ? 994 new PrincipalName("xx" + service.toString()) : 995 service, 996 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET) 997 ); 998 EncTGSRepPart enc_part = new EncTGSRepPart( 999 key, 1000 new LastReq(new LastReqEntry[] { 1001 new LastReqEntry(0, timeAfter(-10)) 1002 }), 1003 body.getNonce(), // TODO: detect replay 1004 timeAfter(3600 * 24), 1005 // Next 5 and last MUST be same with ticket 1006 tFlags, 1007 timeAfter(0), 1008 from, 1009 till, renewTill, 1010 service, 1011 body.addresses, 1012 null 1013 ); 1014 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), 1015 KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY); 1016 TGSRep tgsRep = new TGSRep(null, 1017 cname, 1018 t, 1019 edata); 1020 System.out.println(" Return " + tgsRep.cname 1021 + " ticket for " + tgsRep.ticket.sname + ", flags " 1022 + tFlags); 1023 1024 DerOutputStream out = new DerOutputStream(); 1025 out.write(DerValue.createTag(DerValue.TAG_APPLICATION, 1026 true, (byte)Krb5.KRB_TGS_REP), tgsRep.asn1Encode()); 1027 return out.toByteArray(); 1028 } catch (KrbException ke) { 1029 ke.printStackTrace(System.out); 1030 KRBError kerr = ke.getError(); 1031 KDCReqBody body = tgsReq.reqBody; 1032 System.out.println(" Error " + ke.returnCode() 1033 + " " +ke.returnCodeMessage()); 1034 if (kerr == null) { 1035 kerr = new KRBError(null, null, null, 1036 timeAfter(0), 1037 0, 1038 ke.returnCode(), 1039 body.cname, 1040 service, 1041 KrbException.errorMessage(ke.returnCode()), 1042 null); 1043 } 1044 return kerr.asn1Encode(); 1045 } 1046 } 1047 1048 /** 1049 * Processes a AS_REQ and generates a AS_REP (or KRB_ERROR) 1050 * @param in the request 1051 * @return the response 1052 * @throws java.lang.Exception for various errors 1053 */ processAsReq(byte[] in)1054 protected byte[] processAsReq(byte[] in) throws Exception { 1055 ASReq asReq = new ASReq(in); 1056 byte[] asReqbytes = asReq.asn1Encode(); 1057 int[] eTypes = null; 1058 List<PAData> outPAs = new ArrayList<>(); 1059 1060 PrincipalName service = asReq.reqBody.sname; 1061 if (options.containsKey(KDC.Option.RESP_NT)) { 1062 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT), 1063 service.getNameStrings(), 1064 Realm.getDefault()); 1065 } 1066 try { 1067 System.out.println(realm + "> " + asReq.reqBody.cname + 1068 " sends AS-REQ for " + 1069 service + ", " + asReq.reqBody.kdcOptions); 1070 1071 KDCReqBody body = asReq.reqBody; 1072 1073 eTypes = filterSupported(KDCReqBodyDotEType(body)); 1074 if (eTypes.length == 0) { 1075 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1076 } 1077 int eType = eTypes[0]; 1078 1079 if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) { 1080 PrincipalName principal = alias2Principals.get( 1081 body.cname.getNameString()); 1082 if (principal != null) { 1083 body.cname = principal; 1084 } else { 1085 KDC referral = aliasReferrals.get(body.cname.getNameString()); 1086 if (referral != null) { 1087 body.cname = new PrincipalName( 1088 PrincipalName.TGS_DEFAULT_SRV_NAME, 1089 PrincipalName.KRB_NT_SRV_INST, 1090 referral.getRealm()); 1091 throw new KrbException(Krb5.KRB_ERR_WRONG_REALM); 1092 } 1093 } 1094 } 1095 1096 EncryptionKey ckey = keyForUser(body.cname, eType, false); 1097 EncryptionKey skey = keyForUser(service, eType, true); 1098 1099 if (options.containsKey(KDC.Option.ONLY_RC4_TGT)) { 1100 int tgtEType = EncryptedData.ETYPE_ARCFOUR_HMAC; 1101 boolean found = false; 1102 for (int i=0; i<eTypes.length; i++) { 1103 if (eTypes[i] == tgtEType) { 1104 found = true; 1105 break; 1106 } 1107 } 1108 if (!found) { 1109 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1110 } 1111 skey = keyForUser(service, tgtEType, true); 1112 } 1113 if (ckey == null) { 1114 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP); 1115 } 1116 if (skey == null) { 1117 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO 1118 } 1119 1120 // Session key 1121 EncryptionKey key = generateRandomKey(eType); 1122 // Check time, TODO 1123 KerberosTime from = body.from; 1124 KerberosTime till = body.till; 1125 KerberosTime rtime = body.rtime; 1126 if (from == null || from.isZero()) { 1127 from = timeAfter(0); 1128 } 1129 if (till == null) { 1130 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO 1131 } else if (till.isZero()) { 1132 till = timeAfter(DEFAULT_LIFETIME); 1133 } else if (till.greaterThan(timeAfter(24 * 3600)) 1134 && System.getProperty("test.kdc.force.till") == null) { 1135 // If till is more than 1 day later, make it renewable 1136 till = timeAfter(DEFAULT_LIFETIME); 1137 body.kdcOptions.set(KDCOptions.RENEWABLE, true); 1138 if (rtime == null) rtime = till; 1139 } 1140 if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) { 1141 rtime = timeAfter(DEFAULT_RENEWTIME); 1142 } 1143 //body.from 1144 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; 1145 if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) { 1146 List<String> sensitives = (List<String>) 1147 options.get(Option.SENSITIVE_ACCOUNTS); 1148 if (sensitives != null 1149 && sensitives.contains(body.cname.toString())) { 1150 // Cannot make FORWARDABLE 1151 } else { 1152 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; 1153 } 1154 } 1155 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) { 1156 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true; 1157 //renew = timeAfter(3600 * 24 * 7); 1158 } 1159 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) { 1160 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true; 1161 } 1162 if (body.kdcOptions.get(KDCOptions.POSTDATED)) { 1163 bFlags[Krb5.TKT_OPTS_POSTDATED] = true; 1164 } 1165 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { 1166 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; 1167 } 1168 bFlags[Krb5.TKT_OPTS_INITIAL] = true; 1169 if (System.getProperty("test.kdc.always.enc.pa.rep") != null) { 1170 bFlags[Krb5.TKT_OPTS_ENC_PA_REP] = true; 1171 } 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 "DYLD_LIBRARY_PATH", nativePath + "/lib", 1819 "LD_LIBRARY_PATH", nativePath + "/lib"); 1820 } 1821 1822 @Override addPrincipal(String user, String pass)1823 public void addPrincipal(String user, String pass) { 1824 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1825 "add", "-p", pass, "--use-defaults", user); 1826 } 1827 1828 @Override ktadd(String user, String ktab)1829 public void ktadd(String user, String ktab) { 1830 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1831 "ext_keytab", "-k", ktab, user); 1832 } 1833 1834 @Override init()1835 public void init() { 1836 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm, 1837 "init", "--realm-max-ticket-life=1day", 1838 "--realm-max-renewable-life=1month", realm); 1839 } 1840 1841 @Override kdc()1842 public Process kdc() { 1843 return run(false, nativePath + "/libexec/kdc", 1844 "--addresses=127.0.0.1", "-P", "" + port); 1845 } 1846 1847 @Override prepare()1848 public void prepare() { 1849 try { 1850 Files.createDirectory(Paths.get(base)); 1851 Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList( 1852 "[libdefaults]", 1853 "default_realm = " + realm, 1854 "default_keytab_name = FILE:" + base + "/krb5.keytab", 1855 "forwardable = true", 1856 "dns_lookup_kdc = no", 1857 "dns_lookup_realm = no", 1858 "dns_canonicalize_hostname = false", 1859 "\n[realms]", 1860 realm + " = {", 1861 " kdc = localhost:" + port, 1862 "}", 1863 "\n[kdc]", 1864 "db-dir = " + base, 1865 "database = {", 1866 " label = {", 1867 " dbname = " + base + "/current-db", 1868 " realm = " + realm, 1869 " mkey_file = " + base + "/mkey.file", 1870 " acl_file = " + base + "/heimdal.acl", 1871 " log_file = " + base + "/current.log", 1872 " }", 1873 "}", 1874 SUPPORTED_ETYPES == null ? "" 1875 : ("\n[kadmin]\ndefault_keys = " 1876 + (SUPPORTED_ETYPES + ",") 1877 .replaceAll(",", ":pw-salt ")), 1878 "\n[logging]", 1879 "kdc = 0-/FILE:" + base + "/messages.log", 1880 "krb5 = 0-/FILE:" + base + "/messages.log", 1881 "default = 0-/FILE:" + base + "/messages.log" 1882 )); 1883 } catch (IOException e) { 1884 throw new UncheckedIOException(e); 1885 } 1886 } 1887 1888 @Override kinit(String user, String ccache)1889 void kinit(String user, String ccache) { 1890 String tmpName = base + "/" + user + "." + 1891 System.identityHashCode(this) + ".keytab"; 1892 ktadd(user, tmpName); 1893 run(true, nativePath + "/bin/kinit", 1894 "-f", "-t", tmpName, "-c", ccache, user); 1895 } 1896 } 1897 1898 // MIT krb5 KDC. Make your own exploded (install == false), or 1899 // "make install" into nativePath (install == true). 1900 static class MIT extends NativeKdc { 1901 1902 private boolean install; // "make install" or "make" 1903 MIT(boolean install, String nativePath, KDC kdc)1904 MIT(boolean install, String nativePath, KDC kdc) { 1905 super(nativePath, kdc); 1906 this.install = install; 1907 this.env = Map.of( 1908 "KRB5_KDC_PROFILE", base + "/kdc.conf", 1909 "KRB5_CONFIG", base + "/krb5.conf", 1910 "KRB5_TRACE", "/dev/stderr", 1911 "DYLD_LIBRARY_PATH", nativePath + "/lib", 1912 "LD_LIBRARY_PATH", 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