678N/A/*
3909N/A * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
678N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
678N/A *
678N/A * This code is free software; you can redistribute it and/or modify it
678N/A * under the terms of the GNU General Public License version 2 only, as
678N/A * published by the Free Software Foundation.
678N/A *
678N/A * This code is distributed in the hope that it will be useful, but WITHOUT
678N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
678N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
678N/A * version 2 for more details (a copy is included in the LICENSE file that
678N/A * accompanied this code).
678N/A *
678N/A * You should have received a copy of the GNU General Public License version
678N/A * 2 along with this work; if not, write to the Free Software Foundation,
678N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
678N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
678N/A */
678N/A
678N/Aimport java.lang.reflect.Constructor;
678N/Aimport java.lang.reflect.Field;
678N/Aimport java.lang.reflect.InvocationTargetException;
678N/Aimport java.net.*;
678N/Aimport java.io.*;
678N/Aimport java.lang.reflect.Method;
678N/Aimport java.security.SecureRandom;
678N/Aimport java.util.*;
678N/Aimport java.util.concurrent.*;
1300N/Aimport sun.net.spi.nameservice.NameService;
1300N/Aimport sun.net.spi.nameservice.NameServiceDescriptor;
678N/Aimport sun.security.krb5.*;
678N/Aimport sun.security.krb5.internal.*;
704N/Aimport sun.security.krb5.internal.ccache.CredentialsCache;
678N/Aimport sun.security.krb5.internal.crypto.KeyUsage;
678N/Aimport sun.security.krb5.internal.ktab.KeyTab;
678N/Aimport sun.security.util.DerInputStream;
678N/Aimport sun.security.util.DerOutputStream;
678N/Aimport sun.security.util.DerValue;
678N/A
678N/A/**
678N/A * A KDC server.
678N/A * <p>
678N/A * Features:
678N/A * <ol>
678N/A * <li> Supports TCP and UDP
678N/A * <li> Supports AS-REQ and TGS-REQ
678N/A * <li> Principal db and other settings hard coded in application
678N/A * <li> Options, say, request preauth or not
678N/A * </ol>
678N/A * Side effects:
678N/A * <ol>
678N/A * <li> The Sun-internal class <code>sun.security.krb5.Config</code> is a
678N/A * singleton and initialized according to Kerberos settings (krb5.conf and
678N/A * java.security.krb5.* system properties). This means once it's initialized
678N/A * it will not automatically notice any changes to these settings (or file
678N/A * changes of krb5.conf). The KDC class normally does not touch these
678N/A * settings (except for the <code>writeKtab()</code> method). However, to make
678N/A * sure nothing ever goes wrong, if you want to make any changes to these
678N/A * settings after calling a KDC method, call <code>Config.refresh()</code> to
678N/A * make sure your changes are reflected in the <code>Config</code> object.
678N/A * </ol>
1941N/A * System properties recognized:
1941N/A * <ul>
1941N/A * <li>test.kdc.save.ccache
1941N/A * </ul>
1941N/A * Support policies:
1941N/A * <ul>
1941N/A * <li>ok-as-delegate
1941N/A * </ul>
678N/A * Issues and TODOs:
678N/A * <ol>
678N/A * <li> Generates krb5.conf to be used on another machine, currently the kdc is
678N/A * always localhost
678N/A * <li> More options to KDC, say, error output, say, response nonce !=
678N/A * request nonce
678N/A * </ol>
678N/A * Note: This program uses internal krb5 classes (including reflection to
678N/A * access private fields and methods).
678N/A * <p>
678N/A * Usages:
678N/A * <p>
678N/A * 1. Init and start the KDC:
678N/A * <pre>
678N/A * KDC kdc = KDC.create("REALM.NAME", port, isDaemon);
678N/A * KDC kdc = KDC.create("REALM.NAME");
678N/A * </pre>
678N/A * Here, <code>port</code> is the UDP and TCP port number the KDC server
678N/A * listens on. If zero, a random port is chosen, which you can use getPort()
678N/A * later to retrieve the value.
678N/A * <p>
678N/A * If <code>isDaemon</code> is true, the KDC worker threads will be daemons.
678N/A * <p>
678N/A * The shortcut <code>KDC.create("REALM.NAME")</code> has port=0 and
678N/A * isDaemon=false, and is commonly used in an embedded KDC.
678N/A * <p>
678N/A * 2. Adding users:
678N/A * <pre>
678N/A * kdc.addPrincipal(String principal_name, char[] password);
678N/A * kdc.addPrincipalRandKey(String principal_name);
678N/A * </pre>
678N/A * A service principal's name should look like "host/f.q.d.n". The second form
678N/A * generates a random key. To expose this key, call <code>writeKtab()</code> to
678N/A * save the keys into a keytab file.
678N/A * <p>
678N/A * Note that you need to add the principal name krbtgt/REALM.NAME yourself.
678N/A * <p>
678N/A * Note that you can safely add a principal at any time after the KDC is
678N/A * started and before a user requests info on this principal.
678N/A * <p>
678N/A * 3. Other public methods:
678N/A * <ul>
678N/A * <li> <code>getPort</code>: Returns the port number the KDC uses
678N/A * <li> <code>getRealm</code>: Returns the realm name
678N/A * <li> <code>writeKtab</code>: Writes all principals' keys into a keytab file
678N/A * <li> <code>saveConfig</code>: Saves a krb5.conf file to access this KDC
678N/A * <li> <code>setOption</code>: Sets various options
678N/A * </ul>
678N/A * Read the javadoc for details. Lazy developer can use <code>OneKDC</code>
678N/A * directly.
678N/A */
678N/Apublic class KDC {
678N/A
678N/A // Under the hood.
678N/A
678N/A // The random generator to generate random keys (including session keys)
678N/A private static SecureRandom secureRandom = new SecureRandom();
3054N/A
3054N/A // Principal db. principal -> pass. A case-insensitive TreeMap is used
3054N/A // so that even if the client provides a name with different case, the KDC
3054N/A // can still locate the principal and give back correct salt.
3388N/A private TreeMap<String,char[]> passwords = new TreeMap<>
3054N/A (String.CASE_INSENSITIVE_ORDER);
3054N/A
678N/A // Realm name
678N/A private String realm;
1300N/A // KDC
1300N/A private String kdc;
1300N/A // Service port number
1300N/A private int port;
678N/A // The request/response job queue
3388N/A private BlockingQueue<Job> q = new ArrayBlockingQueue<>(100);
678N/A // Options
3388N/A private Map<Option,Object> options = new HashMap<>();
678N/A
2034N/A private Thread thread1, thread2, thread3;
2034N/A DatagramSocket u1 = null;
2034N/A ServerSocket t1 = null;
2034N/A
678N/A /**
678N/A * Option names, to be expanded forever.
678N/A */
678N/A public static enum Option {
678N/A /**
678N/A * Whether pre-authentication is required. Default Boolean.TRUE
678N/A */
678N/A PREAUTH_REQUIRED,
2461N/A /**
2489N/A * Only issue TGT in RC4
2461N/A */
2461N/A ONLY_RC4_TGT,
2489N/A /**
3054N/A * Use RC4 as the first in preauth
2489N/A */
3054N/A RC4_FIRST_PREAUTH,
3054N/A /**
3054N/A * Use only one preauth, so that some keys are not easy to generate
3054N/A */
3054N/A ONLY_ONE_PREAUTH,
4351N/A /**
4351N/A * Set all name-type to a value in response
4351N/A */
4351N/A RESP_NT,
4391N/A /**
4391N/A * Multiple ETYPE-INFO-ENTRY with same etype but different salt
4391N/A */
4391N/A DUP_ETYPE,
678N/A };
678N/A
1300N/A static {
1300N/A System.setProperty("sun.net.spi.nameservice.provider.1", "ns,mock");
1300N/A }
1300N/A
678N/A /**
678N/A * A standalone KDC server.
678N/A */
678N/A public static void main(String[] args) throws Exception {
1941N/A KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", 0, false);
678N/A kdc.addPrincipal("dummy", "bogus".toCharArray());
678N/A kdc.addPrincipal("foo", "bar".toCharArray());
1300N/A kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE");
1300N/A kdc.addPrincipalRandKey("server/host.rabbit.hole");
1300N/A kdc.addPrincipalRandKey("backend/host.rabbit.hole");
1300N/A KDC.saveConfig("krb5.conf", kdc, "forwardable = true");
678N/A }
678N/A
678N/A /**
678N/A * Creates and starts a KDC running as a daemon on a random port.
678N/A * @param realm the realm name
678N/A * @return the running KDC instance
678N/A * @throws java.io.IOException for any socket creation error
678N/A */
678N/A public static KDC create(String realm) throws IOException {
1300N/A return create(realm, "kdc." + realm.toLowerCase(), 0, true);
678N/A }
678N/A
3054N/A public static KDC existing(String realm, String kdc, int port) {
3054N/A KDC k = new KDC(realm, kdc);
3054N/A k.port = port;
3054N/A return k;
3054N/A }
3054N/A
678N/A /**
678N/A * Creates and starts a KDC server.
678N/A * @param realm the realm name
678N/A * @param port the TCP and UDP port to listen to. A random port will to
678N/A * chosen if zero.
678N/A * @param asDaemon if true, KDC threads will be daemons. Otherwise, not.
678N/A * @return the running KDC instance
678N/A * @throws java.io.IOException for any socket creation error
678N/A */
1300N/A public static KDC create(String realm, String kdc, int port, boolean asDaemon) throws IOException {
1300N/A return new KDC(realm, kdc, port, asDaemon);
678N/A }
678N/A
678N/A /**
678N/A * Sets an option
678N/A * @param key the option name
678N/A * @param obj the value
678N/A */
678N/A public void setOption(Option key, Object value) {
678N/A options.put(key, value);
678N/A }
678N/A
678N/A /**
4102N/A * Writes or appends KDC keys into a keytab. See doc for writeMultiKtab.
4102N/A * @param append true if append, otherwise, overwrite.
4102N/A */
4102N/A private static void writeKtab0(String tab, boolean append, KDC... kdcs)
4102N/A throws IOException, KrbException {
4102N/A KeyTab ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab);
4102N/A for (KDC kdc: kdcs) {
4102N/A for (String name : kdc.passwords.keySet()) {
4102N/A char[] pass = kdc.passwords.get(name);
4102N/A int kvno = 0;
4102N/A if (Character.isDigit(pass[pass.length-1])) {
4102N/A kvno = pass[pass.length-1] - '0';
4102N/A }
4102N/A ktab.addEntry(new PrincipalName(name,
4102N/A name.indexOf('/') < 0 ?
4102N/A PrincipalName.KRB_NT_UNKNOWN :
4102N/A PrincipalName.KRB_NT_SRV_HST),
4102N/A pass,
4102N/A kvno,
4102N/A true);
4102N/A }
4102N/A }
4102N/A ktab.save();
4102N/A }
4102N/A
4102N/A /**
4102N/A * Writes all principals' keys from multiple KDCs into one keytab file.
1266N/A * Note that the keys for the krbtgt principals will not be written.
678N/A * <p>
678N/A * Attention: This method references krb5.conf settings. If you need to
678N/A * setup krb5.conf later, please call <code>Config.refresh()</code> after
678N/A * the new setting. For example:
678N/A * <pre>
1266N/A * KDC.writeKtab("/etc/kdc/ktab", kdc); // Config is initialized,
678N/A * System.setProperty("java.security.krb5.conf", "/home/mykrb5.conf");
678N/A * Config.refresh();
678N/A * </pre>
678N/A *
678N/A * Inside this method there are 2 places krb5.conf is used:
678N/A * <ol>
678N/A * <li> (Fatal) Generating keys: EncryptionKey.acquireSecretKeys
678N/A * <li> (Has workaround) Creating PrincipalName
678N/A * </ol>
678N/A * @param tab The keytab filename to write to.
678N/A * @throws java.io.IOException for any file output error
678N/A * @throws sun.security.krb5.KrbException for any realm and/or principal
678N/A * name error.
678N/A */
1266N/A public static void writeMultiKtab(String tab, KDC... kdcs)
1266N/A throws IOException, KrbException {
4102N/A writeKtab0(tab, false, kdcs);
4102N/A }
4102N/A
4102N/A /**
4102N/A * Appends all principals' keys from multiple KDCs to one keytab file.
4102N/A * See writeMultiKtab for details.
4102N/A */
4102N/A public static void appendMultiKtab(String tab, KDC... kdcs)
4102N/A throws IOException, KrbException {
4102N/A writeKtab0(tab, true, kdcs);
678N/A }
678N/A
678N/A /**
1266N/A * Write a ktab for this KDC.
1266N/A */
1266N/A public void writeKtab(String tab) throws IOException, KrbException {
1266N/A KDC.writeMultiKtab(tab, this);
1266N/A }
1266N/A
1266N/A /**
4102N/A * Appends keys in this KDC to a ktab.
4102N/A */
4102N/A public void appendKtab(String tab) throws IOException, KrbException {
4102N/A KDC.appendMultiKtab(tab, this);
4102N/A }
4102N/A
4102N/A /**
678N/A * Adds a new principal to this realm with a given password.
678N/A * @param user the principal's name. For a service principal, use the
678N/A * form of host/f.q.d.n
678N/A * @param pass the password for the principal
678N/A */
678N/A public void addPrincipal(String user, char[] pass) {
1300N/A if (user.indexOf('@') < 0) {
1300N/A user = user + "@" + realm;
1300N/A }
678N/A passwords.put(user, pass);
678N/A }
678N/A
678N/A /**
678N/A * Adds a new principal to this realm with a random password
678N/A * @param user the principal's name. For a service principal, use the
678N/A * form of host/f.q.d.n
678N/A */
678N/A public void addPrincipalRandKey(String user) {
1300N/A addPrincipal(user, randomPassword());
678N/A }
678N/A
678N/A /**
678N/A * Returns the name of this realm
678N/A * @return the name of this realm
678N/A */
678N/A public String getRealm() {
678N/A return realm;
678N/A }
678N/A
678N/A /**
1300N/A * Returns the name of kdc
1300N/A * @return the name of kdc
1300N/A */
1300N/A public String getKDC() {
1300N/A return kdc;
1300N/A }
1300N/A
1300N/A /**
678N/A * Writes a krb5.conf for one or more KDC that includes KDC locations for
678N/A * each realm and the default realm name. You can also add extra strings
678N/A * into the file. The method should be called like:
678N/A * <pre>
678N/A * KDC.saveConfig("krb5.conf", kdc1, kdc2, ..., line1, line2, ...);
678N/A * </pre>
678N/A * Here you can provide one or more kdc# and zero or more line# arguments.
678N/A * The line# will be put after [libdefaults] and before [realms]. Therefore
678N/A * you can append new lines into [libdefaults] and/or create your new
678N/A * stanzas as well. Note that a newline character will be appended to
678N/A * each line# argument.
678N/A * <p>
678N/A * For example:
678N/A * <pre>
678N/A * KDC.saveConfig("krb5.conf", this);
678N/A * </pre>
678N/A * generates:
678N/A * <pre>
678N/A * [libdefaults]
678N/A * default_realm = REALM.NAME
678N/A *
678N/A * [realms]
678N/A * REALM.NAME = {
1300N/A * kdc = host:port_number
678N/A * }
678N/A * </pre>
678N/A *
678N/A * Another example:
678N/A * <pre>
678N/A * KDC.saveConfig("krb5.conf", kdc1, kdc2, "forwardable = true", "",
678N/A * "[domain_realm]",
678N/A * ".kdc1.com = KDC1.NAME");
678N/A * </pre>
678N/A * generates:
678N/A * <pre>
678N/A * [libdefaults]
678N/A * default_realm = KDC1.NAME
678N/A * forwardable = true
678N/A *
678N/A * [domain_realm]
678N/A * .kdc1.com = KDC1.NAME
678N/A *
678N/A * [realms]
678N/A * KDC1.NAME = {
1300N/A * kdc = host:port1
678N/A * }
678N/A * KDC2.NAME = {
1300N/A * kdc = host:port2
678N/A * }
678N/A * </pre>
678N/A * @param file the name of the file to write into
678N/A * @param kdc the first (and default) KDC
678N/A * @param more more KDCs or extra lines (in their appearing order) to
678N/A * insert into the krb5.conf file. This method reads each argument's type
678N/A * to determine what it's for. This argument can be empty.
678N/A * @throws java.io.IOException for any file output error
678N/A */
678N/A public static void saveConfig(String file, KDC kdc, Object... more)
678N/A throws IOException {
678N/A File f = new File(file);
678N/A StringBuffer sb = new StringBuffer();
678N/A sb.append("[libdefaults]\ndefault_realm = ");
678N/A sb.append(kdc.realm);
678N/A sb.append("\n");
678N/A for (Object o: more) {
678N/A if (o instanceof String) {
678N/A sb.append(o);
678N/A sb.append("\n");
678N/A }
678N/A }
678N/A sb.append("\n[realms]\n");
678N/A sb.append(realmLineForKDC(kdc));
678N/A for (Object o: more) {
678N/A if (o instanceof KDC) {
678N/A sb.append(realmLineForKDC((KDC)o));
678N/A }
678N/A }
678N/A FileOutputStream fos = new FileOutputStream(f);
678N/A fos.write(sb.toString().getBytes());
678N/A fos.close();
678N/A }
678N/A
678N/A /**
678N/A * Returns the service port of the KDC server.
678N/A * @return the KDC service port
678N/A */
678N/A public int getPort() {
678N/A return port;
678N/A }
678N/A
678N/A // Private helper methods
678N/A
678N/A /**
678N/A * Private constructor, cannot be called outside.
678N/A * @param realm
678N/A */
1300N/A private KDC(String realm, String kdc) {
678N/A this.realm = realm;
1300N/A this.kdc = kdc;
678N/A }
678N/A
678N/A /**
678N/A * A constructor that starts the KDC service also.
678N/A */
1300N/A protected KDC(String realm, String kdc, int port, boolean asDaemon)
678N/A throws IOException {
1300N/A this(realm, kdc);
678N/A startServer(port, asDaemon);
678N/A }
678N/A /**
678N/A * Generates a 32-char random password
678N/A * @return the password
678N/A */
678N/A private static char[] randomPassword() {
678N/A char[] pass = new char[32];
2413N/A for (int i=0; i<31; i++)
678N/A pass[i] = (char)secureRandom.nextInt();
2413N/A // The last char cannot be a number, otherwise, keyForUser()
2413N/A // believes it's a sign of kvno
2413N/A pass[31] = 'Z';
678N/A return pass;
678N/A }
678N/A
678N/A /**
678N/A * Generates a random key for the given encryption type.
678N/A * @param eType the encryption type
678N/A * @return the generated key
678N/A * @throws sun.security.krb5.KrbException for unknown/unsupported etype
678N/A */
678N/A private static EncryptionKey generateRandomKey(int eType)
678N/A throws KrbException {
678N/A // Is 32 enough for AES256? I should have generated the keys directly
678N/A // but different cryptos have different rules on what keys are valid.
678N/A char[] pass = randomPassword();
678N/A String algo;
678N/A switch (eType) {
678N/A case EncryptedData.ETYPE_DES_CBC_MD5: algo = "DES"; break;
678N/A case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: algo = "DESede"; break;
678N/A case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: algo = "AES128"; break;
678N/A case EncryptedData.ETYPE_ARCFOUR_HMAC: algo = "ArcFourHMAC"; break;
678N/A case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: algo = "AES256"; break;
678N/A default: algo = "DES"; break;
678N/A }
678N/A return new EncryptionKey(pass, "NOTHING", algo); // Silly
678N/A }
678N/A
678N/A /**
678N/A * Returns the password for a given principal
678N/A * @param p principal
678N/A * @return the password
678N/A * @throws sun.security.krb5.KrbException when the principal is not inside
678N/A * the database.
678N/A */
1941N/A private char[] getPassword(PrincipalName p, boolean server)
1941N/A throws KrbException {
1300N/A String pn = p.toString();
1300N/A if (p.getRealmString() == null) {
1300N/A pn = pn + "@" + getRealm();
1300N/A }
1300N/A char[] pass = passwords.get(pn);
678N/A if (pass == null) {
1941N/A throw new KrbException(server?
1941N/A Krb5.KDC_ERR_S_PRINCIPAL_UNKNOWN:
1941N/A Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN);
678N/A }
678N/A return pass;
678N/A }
678N/A
678N/A /**
1300N/A * Returns the salt string for the principal.
678N/A * @param p principal
678N/A * @return the salt
678N/A */
678N/A private String getSalt(PrincipalName p) {
3054N/A String pn = p.toString();
3054N/A if (p.getRealmString() == null) {
3054N/A pn = pn + "@" + getRealm();
3054N/A }
3054N/A if (passwords.containsKey(pn)) {
3054N/A try {
3054N/A // Find the principal name with correct case.
3054N/A p = new PrincipalName(passwords.ceilingEntry(pn).getKey());
3054N/A } catch (RealmException re) {
3054N/A // Won't happen
3054N/A }
3054N/A }
1300N/A String s = p.getRealmString();
1300N/A if (s == null) s = getRealm();
1300N/A for (String n: p.getNameStrings()) {
1300N/A s += n;
678N/A }
1300N/A return s;
678N/A }
678N/A
678N/A /**
678N/A * Returns the key for a given principal of the given encryption type
678N/A * @param p the principal
678N/A * @param etype the encryption type
1941N/A * @param server looking for a server principal?
678N/A * @return the key
678N/A * @throws sun.security.krb5.KrbException for unknown/unsupported etype
678N/A */
1941N/A private EncryptionKey keyForUser(PrincipalName p, int etype, boolean server)
1941N/A throws KrbException {
678N/A try {
678N/A // Do not call EncryptionKey.acquireSecretKeys(), otherwise
678N/A // the krb5.conf config file would be loaded.
1802N/A Integer kvno = null;
2035N/A // For service whose password ending with a number, use it as kvno.
2035N/A // Kvno must be postive.
2035N/A if (p.toString().indexOf('/') > 0) {
1941N/A char[] pass = getPassword(p, server);
1802N/A if (Character.isDigit(pass[pass.length-1])) {
1802N/A kvno = pass[pass.length-1] - '0';
1802N/A }
1802N/A }
3054N/A return new EncryptionKey(EncryptionKeyDotStringToKey(
3054N/A getPassword(p, server), getSalt(p), null, etype),
1802N/A etype, kvno);
1941N/A } catch (KrbException ke) {
1941N/A throw ke;
678N/A } catch (Exception e) {
678N/A throw new RuntimeException(e); // should not happen
678N/A }
678N/A }
678N/A
3388N/A private Map<String,String> policies = new HashMap<>();
1941N/A
1941N/A public void setPolicy(String rule, String value) {
1941N/A if (value == null) {
1941N/A policies.remove(rule);
1941N/A } else {
1941N/A policies.put(rule, value);
1941N/A }
1941N/A }
1941N/A /**
1941N/A * If the provided client/server pair matches a rule
1941N/A *
1941N/A * A system property named test.kdc.policy.RULE will be consulted.
1941N/A * If it's unset, returns false. If its value is "", any pair is
1941N/A * matched. Otherwise, it should contains the server name matched.
1941N/A *
1941N/A * TODO: client name is not used currently.
1941N/A *
1941N/A * @param c client name
1941N/A * @param s server name
1941N/A * @param rule rule name
1941N/A * @return if a match is found
1941N/A */
1941N/A private boolean configMatch(String c, String s, String rule) {
1941N/A String policy = policies.get(rule);
1941N/A boolean result = false;
1941N/A if (policy == null) {
1941N/A result = false;
1941N/A } else if (policy.length() == 0) {
1941N/A result = true;
1941N/A } else {
1941N/A String[] names = policy.split("\\s+");
1941N/A for (String name: names) {
1941N/A if (name.equals(s)) {
1941N/A result = true;
1941N/A break;
1941N/A }
1941N/A }
1941N/A }
1941N/A if (result) {
1941N/A System.out.printf(">>>> Policy match result (%s vs %s on %s) %b\n",
1941N/A c, s, rule, result);
1941N/A }
1941N/A return result;
1941N/A }
1941N/A
1941N/A
678N/A /**
678N/A * Processes an incoming request and generates a response.
678N/A * @param in the request
678N/A * @return the response
678N/A * @throws java.lang.Exception for various errors
678N/A */
678N/A private byte[] processMessage(byte[] in) throws Exception {
678N/A if ((in[0] & 0x1f) == Krb5.KRB_AS_REQ)
678N/A return processAsReq(in);
678N/A else
678N/A return processTgsReq(in);
678N/A }
678N/A
678N/A /**
678N/A * Processes a TGS_REQ and generates a TGS_REP (or KRB_ERROR)
678N/A * @param in the request
678N/A * @return the response
678N/A * @throws java.lang.Exception for various errors
678N/A */
678N/A private byte[] processTgsReq(byte[] in) throws Exception {
678N/A TGSReq tgsReq = new TGSReq(in);
4351N/A PrincipalName service = tgsReq.reqBody.sname;
4351N/A if (options.containsKey(KDC.Option.RESP_NT)) {
4351N/A service = new PrincipalName(service.getNameStrings(),
4351N/A (int)options.get(KDC.Option.RESP_NT));
4351N/A service.setRealm(service.getRealm());
4351N/A }
678N/A try {
678N/A System.out.println(realm + "> " + tgsReq.reqBody.cname +
678N/A " sends TGS-REQ for " +
4351N/A service);
678N/A KDCReqBody body = tgsReq.reqBody;
3054N/A int[] eTypes = KDCReqBodyDotEType(body);
3054N/A int e2 = eTypes[0]; // etype for outgoing session key
3054N/A int e3 = eTypes[0]; // etype for outgoing ticket
678N/A
3054N/A PAData[] pas = kDCReqDotPAData(tgsReq);
678N/A
678N/A Ticket tkt = null;
678N/A EncTicketPart etp = null;
678N/A if (pas == null || pas.length == 0) {
678N/A throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);
678N/A } else {
678N/A for (PAData pa: pas) {
678N/A if (pa.getType() == Krb5.PA_TGS_REQ) {
678N/A APReq apReq = new APReq(pa.getValue());
678N/A EncryptedData ed = apReq.authenticator;
678N/A tkt = apReq.ticket;
3054N/A int te = tkt.encPart.getEType();
1300N/A tkt.sname.setRealm(tkt.realm);
3054N/A EncryptionKey kkey = keyForUser(tkt.sname, te, true);
678N/A byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET);
678N/A DerInputStream derIn = new DerInputStream(bb);
678N/A DerValue der = derIn.getDerValue();
678N/A etp = new EncTicketPart(der.toByteArray());
678N/A }
678N/A }
678N/A if (tkt == null) {
678N/A throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);
678N/A }
678N/A }
678N/A
678N/A // Session key for original ticket, TGT
678N/A EncryptionKey ckey = etp.key;
678N/A
678N/A // Session key for session with the service
3054N/A EncryptionKey key = generateRandomKey(e2);
678N/A
678N/A // Check time, TODO
678N/A KerberosTime till = body.till;
678N/A if (till == null) {
678N/A throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
678N/A } else if (till.isZero()) {
678N/A till = new KerberosTime(new Date().getTime() + 1000 * 3600 * 11);
678N/A }
678N/A
678N/A boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
678N/A if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) {
678N/A bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true;
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.FORWARDED) ||
678N/A etp.flags.get(Krb5.TKT_OPTS_FORWARDED)) {
678N/A bFlags[Krb5.TKT_OPTS_FORWARDED] = true;
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.RENEWABLE)) {
678N/A bFlags[Krb5.TKT_OPTS_RENEWABLE] = true;
678N/A //renew = new KerberosTime(new Date().getTime() + 1000 * 3600 * 24 * 7);
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {
678N/A bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.POSTDATED)) {
678N/A bFlags[Krb5.TKT_OPTS_POSTDATED] = true;
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) {
678N/A bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true;
678N/A }
1941N/A
4351N/A if (configMatch("", service.getNameString(), "ok-as-delegate")) {
1941N/A bFlags[Krb5.TKT_OPTS_DELEGATE] = true;
1941N/A }
678N/A bFlags[Krb5.TKT_OPTS_INITIAL] = true;
678N/A
678N/A TicketFlags tFlags = new TicketFlags(bFlags);
678N/A EncTicketPart enc = new EncTicketPart(
678N/A tFlags,
678N/A key,
678N/A etp.crealm,
678N/A etp.cname,
678N/A new TransitedEncoding(1, new byte[0]), // TODO
678N/A new KerberosTime(new Date()),
678N/A body.from,
678N/A till, body.rtime,
3982N/A body.addresses != null // always set caddr
3982N/A ? body.addresses
3982N/A : new HostAddresses(
3982N/A new InetAddress[]{InetAddress.getLocalHost()}),
678N/A null);
4351N/A EncryptionKey skey = keyForUser(service, e3, true);
3054N/A if (skey == null) {
3054N/A throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO
3054N/A }
678N/A Ticket t = new Ticket(
678N/A body.crealm,
4351N/A service,
678N/A new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET)
678N/A );
678N/A EncTGSRepPart enc_part = new EncTGSRepPart(
678N/A key,
678N/A new LastReq(new LastReqEntry[]{
678N/A new LastReqEntry(0, new KerberosTime(new Date().getTime() - 10000))
678N/A }),
678N/A body.getNonce(), // TODO: detect replay
678N/A new KerberosTime(new Date().getTime() + 1000 * 3600 * 24),
678N/A // Next 5 and last MUST be same with ticket
678N/A tFlags,
678N/A new KerberosTime(new Date()),
678N/A body.from,
678N/A till, body.rtime,
678N/A body.crealm,
4351N/A service,
3982N/A body.addresses != null // always set caddr
3982N/A ? body.addresses
3982N/A : new HostAddresses(
3982N/A new InetAddress[]{InetAddress.getLocalHost()})
678N/A );
678N/A EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY);
678N/A TGSRep tgsRep = new TGSRep(null,
678N/A etp.crealm,
678N/A etp.cname,
678N/A t,
678N/A edata);
678N/A System.out.println(" Return " + tgsRep.cname
678N/A + " ticket for " + tgsRep.ticket.sname);
678N/A
678N/A DerOutputStream out = new DerOutputStream();
678N/A out.write(DerValue.createTag(DerValue.TAG_APPLICATION,
678N/A true, (byte)Krb5.KRB_TGS_REP), tgsRep.asn1Encode());
678N/A return out.toByteArray();
678N/A } catch (KrbException ke) {
678N/A ke.printStackTrace(System.out);
678N/A KRBError kerr = ke.getError();
678N/A KDCReqBody body = tgsReq.reqBody;
678N/A System.out.println(" Error " + ke.returnCode()
678N/A + " " +ke.returnCodeMessage());
678N/A if (kerr == null) {
678N/A kerr = new KRBError(null, null, null,
678N/A new KerberosTime(new Date()),
678N/A 0,
678N/A ke.returnCode(),
678N/A body.crealm, body.cname,
4351N/A new Realm(getRealm()), service,
678N/A KrbException.errorMessage(ke.returnCode()),
678N/A null);
678N/A }
678N/A return kerr.asn1Encode();
678N/A }
678N/A }
678N/A
678N/A /**
678N/A * Processes a AS_REQ and generates a AS_REP (or KRB_ERROR)
678N/A * @param in the request
678N/A * @return the response
678N/A * @throws java.lang.Exception for various errors
678N/A */
678N/A private byte[] processAsReq(byte[] in) throws Exception {
678N/A ASReq asReq = new ASReq(in);
678N/A int[] eTypes = null;
3388N/A List<PAData> outPAs = new ArrayList<>();
3054N/A
4351N/A PrincipalName service = asReq.reqBody.sname;
4351N/A if (options.containsKey(KDC.Option.RESP_NT)) {
4351N/A service = new PrincipalName(service.getNameStrings(),
4351N/A (int)options.get(KDC.Option.RESP_NT));
4351N/A service.setRealm(service.getRealm());
4351N/A }
678N/A try {
678N/A System.out.println(realm + "> " + asReq.reqBody.cname +
678N/A " sends AS-REQ for " +
4351N/A service);
678N/A
678N/A KDCReqBody body = asReq.reqBody;
3054N/A body.cname.setRealm(getRealm());
678N/A
3054N/A eTypes = KDCReqBodyDotEType(body);
678N/A int eType = eTypes[0];
678N/A
1941N/A EncryptionKey ckey = keyForUser(body.cname, eType, false);
4351N/A EncryptionKey skey = keyForUser(service, eType, true);
2461N/A
2461N/A if (options.containsKey(KDC.Option.ONLY_RC4_TGT)) {
2461N/A int tgtEType = EncryptedData.ETYPE_ARCFOUR_HMAC;
2461N/A boolean found = false;
2461N/A for (int i=0; i<eTypes.length; i++) {
2461N/A if (eTypes[i] == tgtEType) {
2461N/A found = true;
2461N/A break;
2461N/A }
2461N/A }
2461N/A if (!found) {
2461N/A throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
2461N/A }
4351N/A skey = keyForUser(service, tgtEType, true);
2461N/A }
678N/A if (ckey == null) {
678N/A throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
678N/A }
678N/A if (skey == null) {
678N/A throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO
678N/A }
678N/A
678N/A // Session key
678N/A EncryptionKey key = generateRandomKey(eType);
678N/A // Check time, TODO
678N/A KerberosTime till = body.till;
678N/A if (till == null) {
678N/A throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
678N/A } else if (till.isZero()) {
678N/A till = new KerberosTime(new Date().getTime() + 1000 * 3600 * 11);
678N/A }
678N/A //body.from
678N/A boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
678N/A if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) {
678N/A bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true;
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.RENEWABLE)) {
678N/A bFlags[Krb5.TKT_OPTS_RENEWABLE] = true;
678N/A //renew = new KerberosTime(new Date().getTime() + 1000 * 3600 * 24 * 7);
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {
678N/A bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.POSTDATED)) {
678N/A bFlags[Krb5.TKT_OPTS_POSTDATED] = true;
678N/A }
678N/A if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) {
678N/A bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true;
678N/A }
678N/A bFlags[Krb5.TKT_OPTS_INITIAL] = true;
678N/A
3054N/A // Creating PA-DATA
4391N/A DerValue[] pas2 = null, pas = null;
4391N/A if (options.containsKey(KDC.Option.DUP_ETYPE)) {
4391N/A int n = (Integer)options.get(KDC.Option.DUP_ETYPE);
4391N/A switch (n) {
4391N/A case 1: // customer's case in 7067974
4391N/A pas2 = new DerValue[] {
4391N/A new DerValue(new ETypeInfo2(1, null, null).asn1Encode()),
4391N/A new DerValue(new ETypeInfo2(1, "", null).asn1Encode()),
4391N/A new DerValue(new ETypeInfo2(1, OneKDC.REALM, new byte[]{1}).asn1Encode()),
4391N/A };
4391N/A pas = new DerValue[] {
4391N/A new DerValue(new ETypeInfo(1, null).asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, "").asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, OneKDC.REALM).asn1Encode()),
4391N/A };
4391N/A break;
4391N/A case 2: // we still reject non-null s2kparams and prefer E2 over E
4391N/A pas2 = new DerValue[] {
4391N/A new DerValue(new ETypeInfo2(1, OneKDC.REALM, new byte[]{1}).asn1Encode()),
4391N/A new DerValue(new ETypeInfo2(1, null, null).asn1Encode()),
4391N/A new DerValue(new ETypeInfo2(1, "", null).asn1Encode()),
4391N/A };
4391N/A pas = new DerValue[] {
4391N/A new DerValue(new ETypeInfo(1, OneKDC.REALM).asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, null).asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, "").asn1Encode()),
4391N/A };
4391N/A break;
4391N/A case 3: // but only E is wrong
4391N/A pas = new DerValue[] {
4391N/A new DerValue(new ETypeInfo(1, OneKDC.REALM).asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, null).asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, "").asn1Encode()),
4391N/A };
4391N/A break;
4391N/A case 4: // we also ignore rc4-hmac
4391N/A pas = new DerValue[] {
4391N/A new DerValue(new ETypeInfo(23, "ANYTHING").asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, null).asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, "").asn1Encode()),
4391N/A };
4391N/A break;
4391N/A case 5: // "" should be wrong, but we accept it now
4391N/A // See s.s.k.internal.PAData$SaltAndParams
4391N/A pas = new DerValue[] {
4391N/A new DerValue(new ETypeInfo(1, "").asn1Encode()),
4391N/A new DerValue(new ETypeInfo(1, null).asn1Encode()),
4391N/A };
4391N/A break;
4391N/A }
4391N/A } else {
4391N/A int[] epas = eTypes;
4391N/A if (options.containsKey(KDC.Option.RC4_FIRST_PREAUTH)) {
4391N/A for (int i=1; i<epas.length; i++) {
4391N/A if (epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC) {
4391N/A epas[i] = epas[0];
4391N/A epas[0] = EncryptedData.ETYPE_ARCFOUR_HMAC;
4391N/A break;
4391N/A }
4391N/A };
4391N/A } else if (options.containsKey(KDC.Option.ONLY_ONE_PREAUTH)) {
4391N/A epas = new int[] { eTypes[0] };
4391N/A }
4391N/A pas2 = new DerValue[epas.length];
4391N/A for (int i=0; i<epas.length; i++) {
4391N/A pas2[i] = new DerValue(new ETypeInfo2(
4391N/A epas[i],
4391N/A epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ?
4391N/A null : getSalt(body.cname),
4391N/A null).asn1Encode());
4391N/A }
4391N/A boolean allOld = true;
4391N/A for (int i: eTypes) {
4391N/A if (i == EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96 ||
4391N/A i == EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96) {
4391N/A allOld = false;
3054N/A break;
3054N/A }
4391N/A }
4391N/A if (allOld) {
4391N/A pas = new DerValue[epas.length];
4391N/A for (int i=0; i<epas.length; i++) {
4391N/A pas[i] = new DerValue(new ETypeInfo(
4391N/A epas[i],
4391N/A epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ?
4391N/A null : getSalt(body.cname)
4391N/A ).asn1Encode());
4391N/A }
4391N/A }
3054N/A }
3054N/A
4391N/A DerOutputStream eid;
4391N/A if (pas2 != null) {
4391N/A eid = new DerOutputStream();
4391N/A eid.putSequence(pas2);
4391N/A outPAs.add(new PAData(Krb5.PA_ETYPE_INFO2, eid.toByteArray()));
3054N/A }
4391N/A if (pas != null) {
3054N/A eid = new DerOutputStream();
3054N/A eid.putSequence(pas);
3054N/A outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray()));
3054N/A }
3054N/A
3054N/A PAData[] inPAs = kDCReqDotPAData(asReq);
3054N/A if (inPAs == null || inPAs.length == 0) {
678N/A Object preauth = options.get(Option.PREAUTH_REQUIRED);
678N/A if (preauth == null || preauth.equals(Boolean.TRUE)) {
678N/A throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED);
678N/A }
678N/A } else {
678N/A try {
3054N/A EncryptedData data = newEncryptedData(new DerValue(inPAs[0].getValue()));
2461N/A EncryptionKey pakey = keyForUser(body.cname, data.getEType(), false);
2461N/A data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS);
678N/A } catch (Exception e) {
678N/A throw new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED);
678N/A }
678N/A bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true;
678N/A }
678N/A
678N/A TicketFlags tFlags = new TicketFlags(bFlags);
678N/A EncTicketPart enc = new EncTicketPart(
678N/A tFlags,
678N/A key,
678N/A body.crealm,
678N/A body.cname,
678N/A new TransitedEncoding(1, new byte[0]),
678N/A new KerberosTime(new Date()),
678N/A body.from,
678N/A till, body.rtime,
678N/A body.addresses,
678N/A null);
678N/A Ticket t = new Ticket(
678N/A body.crealm,
4351N/A service,
678N/A new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET)
678N/A );
678N/A EncASRepPart enc_part = new EncASRepPart(
678N/A key,
678N/A new LastReq(new LastReqEntry[]{
678N/A new LastReqEntry(0, new KerberosTime(new Date().getTime() - 10000))
678N/A }),
678N/A body.getNonce(), // TODO: detect replay?
678N/A new KerberosTime(new Date().getTime() + 1000 * 3600 * 24),
678N/A // Next 5 and last MUST be same with ticket
678N/A tFlags,
678N/A new KerberosTime(new Date()),
678N/A body.from,
678N/A till, body.rtime,
678N/A body.crealm,
4351N/A service,
678N/A body.addresses
678N/A );
678N/A EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_AS_REP_PART);
3054N/A ASRep asRep = new ASRep(
3054N/A outPAs.toArray(new PAData[outPAs.size()]),
678N/A body.crealm,
678N/A body.cname,
678N/A t,
678N/A edata);
678N/A
678N/A System.out.println(" Return " + asRep.cname
678N/A + " ticket for " + asRep.ticket.sname);
678N/A
678N/A DerOutputStream out = new DerOutputStream();
678N/A out.write(DerValue.createTag(DerValue.TAG_APPLICATION,
678N/A true, (byte)Krb5.KRB_AS_REP), asRep.asn1Encode());
704N/A byte[] result = out.toByteArray();
704N/A
704N/A // Added feature:
704N/A // Write the current issuing TGT into a ccache file specified
704N/A // by the system property below.
704N/A String ccache = System.getProperty("test.kdc.save.ccache");
704N/A if (ccache != null) {
704N/A asRep.encKDCRepPart = enc_part;
704N/A sun.security.krb5.internal.ccache.Credentials credentials =
704N/A new sun.security.krb5.internal.ccache.Credentials(asRep);
704N/A asReq.reqBody.cname.setRealm(getRealm());
704N/A CredentialsCache cache =
704N/A CredentialsCache.create(asReq.reqBody.cname, ccache);
704N/A if (cache == null) {
704N/A throw new IOException("Unable to create the cache file " +
704N/A ccache);
704N/A }
704N/A cache.update(credentials);
704N/A cache.save();
704N/A new File(ccache).deleteOnExit();
704N/A }
704N/A
704N/A return result;
678N/A } catch (KrbException ke) {
678N/A ke.printStackTrace(System.out);
678N/A KRBError kerr = ke.getError();
678N/A KDCReqBody body = asReq.reqBody;
678N/A System.out.println(" Error " + ke.returnCode()
678N/A + " " +ke.returnCodeMessage());
678N/A byte[] eData = null;
678N/A if (kerr == null) {
678N/A if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED ||
678N/A ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) {
678N/A DerOutputStream bytes = new DerOutputStream();
678N/A bytes.write(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]).asn1Encode());
3054N/A for (PAData p: outPAs) {
3054N/A bytes.write(p.asn1Encode());
678N/A }
678N/A DerOutputStream temp = new DerOutputStream();
678N/A temp.write(DerValue.tag_Sequence, bytes);
678N/A eData = temp.toByteArray();
678N/A }
678N/A kerr = new KRBError(null, null, null,
678N/A new KerberosTime(new Date()),
678N/A 0,
678N/A ke.returnCode(),
678N/A body.crealm, body.cname,
4351N/A new Realm(getRealm()), service,
678N/A KrbException.errorMessage(ke.returnCode()),
678N/A eData);
678N/A }
678N/A return kerr.asn1Encode();
678N/A }
678N/A }
678N/A
678N/A /**
678N/A * Generates a line for a KDC to put inside [realms] of krb5.conf
678N/A * @param kdc the KDC
1300N/A * @return REALM.NAME = { kdc = host:port }
678N/A */
678N/A private static String realmLineForKDC(KDC kdc) {
1300N/A return String.format(" %s = {\n kdc = %s:%d\n }\n",
1300N/A kdc.realm,
1300N/A kdc.kdc,
1300N/A kdc.port);
678N/A }
678N/A
678N/A /**
678N/A * Start the KDC service. This server listens on both UDP and TCP using
678N/A * the same port number. It uses three threads to deal with requests.
678N/A * They can be set to daemon threads if requested.
678N/A * @param port the port number to listen to. If zero, a random available
678N/A * port no less than 8000 will be chosen and used.
678N/A * @param asDaemon true if the KDC threads should be daemons
678N/A * @throws java.io.IOException for any communication error
678N/A */
678N/A protected void startServer(int port, boolean asDaemon) throws IOException {
678N/A if (port > 0) {
678N/A u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1"));
678N/A t1 = new ServerSocket(port);
678N/A } else {
678N/A while (true) {
678N/A // Try to find a port number that's both TCP and UDP free
678N/A try {
678N/A port = 8000 + new java.util.Random().nextInt(10000);
678N/A u1 = null;
678N/A u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1"));
678N/A t1 = new ServerSocket(port);
678N/A break;
678N/A } catch (Exception e) {
678N/A if (u1 != null) u1.close();
678N/A }
678N/A }
678N/A }
678N/A final DatagramSocket udp = u1;
678N/A final ServerSocket tcp = t1;
678N/A System.out.println("Start KDC on " + port);
678N/A
678N/A this.port = port;
678N/A
678N/A // The UDP consumer
2034N/A thread1 = new Thread() {
678N/A public void run() {
678N/A while (true) {
678N/A try {
678N/A byte[] inbuf = new byte[8192];
678N/A DatagramPacket p = new DatagramPacket(inbuf, inbuf.length);
678N/A udp.receive(p);
678N/A System.out.println("-----------------------------------------------");
678N/A System.out.println(">>>>> UDP packet received");
678N/A q.put(new Job(processMessage(Arrays.copyOf(inbuf, p.getLength())), udp, p));
678N/A } catch (Exception e) {
678N/A e.printStackTrace();
678N/A }
678N/A }
678N/A }
678N/A };
2034N/A thread1.setDaemon(asDaemon);
2034N/A thread1.start();
678N/A
678N/A // The TCP consumer
2034N/A thread2 = new Thread() {
678N/A public void run() {
678N/A while (true) {
678N/A try {
678N/A Socket socket = tcp.accept();
678N/A System.out.println("-----------------------------------------------");
678N/A System.out.println(">>>>> TCP connection established");
678N/A DataInputStream in = new DataInputStream(socket.getInputStream());
678N/A DataOutputStream out = new DataOutputStream(socket.getOutputStream());
678N/A byte[] token = new byte[in.readInt()];
678N/A in.readFully(token);
678N/A q.put(new Job(processMessage(token), socket, out));
678N/A } catch (Exception e) {
678N/A e.printStackTrace();
678N/A }
678N/A }
678N/A }
678N/A };
2034N/A thread2.setDaemon(asDaemon);
2034N/A thread2.start();
678N/A
678N/A // The dispatcher
2034N/A thread3 = new Thread() {
678N/A public void run() {
678N/A while (true) {
678N/A try {
678N/A q.take().send();
678N/A } catch (Exception e) {
678N/A }
678N/A }
678N/A }
678N/A };
2034N/A thread3.setDaemon(true);
2034N/A thread3.start();
678N/A }
678N/A
2034N/A public void terminate() {
2034N/A try {
2034N/A thread1.stop();
2034N/A thread2.stop();
2034N/A thread3.stop();
2034N/A u1.close();
2034N/A t1.close();
2034N/A } catch (Exception e) {
2034N/A // OK
2034N/A }
2034N/A }
678N/A /**
678N/A * Helper class to encapsulate a job in a KDC.
678N/A */
678N/A private static class Job {
678N/A byte[] token; // The received request at creation time and
678N/A // the response at send time
678N/A Socket s; // The TCP socket from where the request comes
678N/A DataOutputStream out; // The OutputStream of the TCP socket
678N/A DatagramSocket s2; // The UDP socket from where the request comes
678N/A DatagramPacket dp; // The incoming UDP datagram packet
678N/A boolean useTCP; // Whether TCP or UDP is used
678N/A
678N/A // Creates a job object for TCP
678N/A Job(byte[] token, Socket s, DataOutputStream out) {
678N/A useTCP = true;
678N/A this.token = token;
678N/A this.s = s;
678N/A this.out = out;
678N/A }
678N/A
678N/A // Creates a job object for UDP
678N/A Job(byte[] token, DatagramSocket s2, DatagramPacket dp) {
678N/A useTCP = false;
678N/A this.token = token;
678N/A this.s2 = s2;
678N/A this.dp = dp;
678N/A }
678N/A
678N/A // Sends the output back to the client
678N/A void send() {
678N/A try {
678N/A if (useTCP) {
678N/A System.out.println(">>>>> TCP request honored");
678N/A out.writeInt(token.length);
678N/A out.write(token);
678N/A s.close();
678N/A } else {
678N/A System.out.println(">>>>> UDP request honored");
678N/A s2.send(new DatagramPacket(token, token.length, dp.getAddress(), dp.getPort()));
678N/A }
678N/A } catch (Exception e) {
678N/A e.printStackTrace();
678N/A }
678N/A }
678N/A }
1300N/A
1300N/A public static class KDCNameService implements NameServiceDescriptor {
1300N/A @Override
1300N/A public NameService createNameService() throws Exception {
1300N/A NameService ns = new NameService() {
1300N/A @Override
1300N/A public InetAddress[] lookupAllHostAddr(String host)
1300N/A throws UnknownHostException {
1300N/A // Everything is localhost
1300N/A return new InetAddress[]{
1300N/A InetAddress.getByAddress(host, new byte[]{127,0,0,1})
1300N/A };
1300N/A }
1300N/A @Override
1300N/A public String getHostByAddr(byte[] addr)
1300N/A throws UnknownHostException {
1300N/A // No reverse lookup, PrincipalName use original string
1300N/A throw new UnknownHostException();
1300N/A }
1300N/A };
1300N/A return ns;
1300N/A }
1300N/A
1300N/A @Override
1300N/A public String getProviderName() {
1300N/A return "mock";
1300N/A }
1300N/A
1300N/A @Override
1300N/A public String getType() {
1300N/A return "ns";
1300N/A }
1300N/A }
3054N/A
3054N/A // Calling private methods thru reflections
3054N/A private static final Field getPADataField;
3054N/A private static final Field getEType;
3054N/A private static final Constructor<EncryptedData> ctorEncryptedData;
3054N/A private static final Method stringToKey;
3054N/A
3054N/A static {
3054N/A try {
3054N/A ctorEncryptedData = EncryptedData.class.getDeclaredConstructor(DerValue.class);
3054N/A ctorEncryptedData.setAccessible(true);
3054N/A getPADataField = KDCReq.class.getDeclaredField("pAData");
3054N/A getPADataField.setAccessible(true);
3054N/A getEType = KDCReqBody.class.getDeclaredField("eType");
3054N/A getEType.setAccessible(true);
3054N/A stringToKey = EncryptionKey.class.getDeclaredMethod(
3054N/A "stringToKey",
3054N/A char[].class, String.class, byte[].class, Integer.TYPE);
3054N/A stringToKey.setAccessible(true);
3054N/A } catch (NoSuchFieldException nsfe) {
3054N/A throw new AssertionError(nsfe);
3054N/A } catch (NoSuchMethodException nsme) {
3054N/A throw new AssertionError(nsme);
3054N/A }
3054N/A }
3054N/A private EncryptedData newEncryptedData(DerValue der) {
3054N/A try {
3054N/A return ctorEncryptedData.newInstance(der);
3054N/A } catch (Exception e) {
3054N/A throw new AssertionError(e);
3054N/A }
3054N/A }
3054N/A private static PAData[] kDCReqDotPAData(KDCReq req) {
3054N/A try {
3054N/A return (PAData[])getPADataField.get(req);
3054N/A } catch (Exception e) {
3054N/A throw new AssertionError(e);
3054N/A }
3054N/A }
3054N/A private static int[] KDCReqBodyDotEType(KDCReqBody body) {
3054N/A try {
3054N/A return (int[]) getEType.get(body);
3054N/A } catch (Exception e) {
3054N/A throw new AssertionError(e);
3054N/A }
3054N/A }
3054N/A private static byte[] EncryptionKeyDotStringToKey(char[] password, String salt,
3054N/A byte[] s2kparams, int keyType) throws KrbCryptoException {
3054N/A try {
3054N/A return (byte[])stringToKey.invoke(
3054N/A null, password, salt, s2kparams, keyType);
3054N/A } catch (InvocationTargetException ex) {
3054N/A throw (KrbCryptoException)ex.getCause();
3054N/A } catch (Exception e) {
3054N/A throw new AssertionError(e);
3054N/A }
3054N/A }
678N/A}