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