Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/sun/security/krb5/auto/KDC.java
38853 views
/*1* Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.lang.reflect.Constructor;24import java.lang.reflect.Field;25import java.lang.reflect.InvocationTargetException;26import java.net.*;27import java.io.*;28import java.lang.reflect.Method;29import java.nio.file.Files;30import java.nio.file.Paths;31import java.util.*;32import java.util.concurrent.*;33import java.util.stream.Collectors;34import java.util.stream.Stream;3536import sun.net.spi.nameservice.NameService;37import sun.net.spi.nameservice.NameServiceDescriptor;38import sun.security.krb5.*;39import sun.security.krb5.internal.*;40import sun.security.krb5.internal.ccache.CredentialsCache;41import sun.security.krb5.internal.crypto.EType;42import sun.security.krb5.internal.crypto.KeyUsage;43import sun.security.krb5.internal.ktab.KeyTab;44import sun.security.util.DerInputStream;45import sun.security.util.DerOutputStream;46import sun.security.util.DerValue;47import java.util.regex.Matcher;48import java.util.regex.Pattern;4950/**51* A KDC server.52*53* Note: By setting the system property native.kdc.path to a native54* krb5 installation, this class starts a native KDC with the55* given realm and host. It can also add new principals and save keytabs.56* Other features might not be available.57* <p>58* Features:59* <ol>60* <li> Supports TCP and UDP61* <li> Supports AS-REQ and TGS-REQ62* <li> Principal db and other settings hard coded in application63* <li> Options, say, request preauth or not64* </ol>65* Side effects:66* <ol>67* <li> The Sun-internal class <code>sun.security.krb5.Config</code> is a68* singleton and initialized according to Kerberos settings (krb5.conf and69* java.security.krb5.* system properties). This means once it's initialized70* it will not automatically notice any changes to these settings (or file71* changes of krb5.conf). The KDC class normally does not touch these72* settings (except for the <code>writeKtab()</code> method). However, to make73* sure nothing ever goes wrong, if you want to make any changes to these74* settings after calling a KDC method, call <code>Config.refresh()</code> to75* make sure your changes are reflected in the <code>Config</code> object.76* </ol>77* System properties recognized:78* <ul>79* <li>test.kdc.save.ccache80* </ul>81* Issues and TODOs:82* <ol>83* <li> Generates krb5.conf to be used on another machine, currently the kdc is84* always localhost85* <li> More options to KDC, say, error output, say, response nonce !=86* request nonce87* </ol>88* Note: This program uses internal krb5 classes (including reflection to89* access private fields and methods).90* <p>91* Usages:92* <p>93* 1. Init and start the KDC:94* <pre>95* KDC kdc = KDC.create("REALM.NAME", port, isDaemon);96* KDC kdc = KDC.create("REALM.NAME");97* </pre>98* Here, <code>port</code> is the UDP and TCP port number the KDC server99* listens on. If zero, a random port is chosen, which you can use getPort()100* later to retrieve the value.101* <p>102* If <code>isDaemon</code> is true, the KDC worker threads will be daemons.103* <p>104* The shortcut <code>KDC.create("REALM.NAME")</code> has port=0 and105* isDaemon=false, and is commonly used in an embedded KDC.106* <p>107* 2. Adding users:108* <pre>109* kdc.addPrincipal(String principal_name, char[] password);110* kdc.addPrincipalRandKey(String principal_name);111* </pre>112* A service principal's name should look like "host/f.q.d.n". The second form113* generates a random key. To expose this key, call <code>writeKtab()</code> to114* save the keys into a keytab file.115* <p>116* Note that you need to add the principal name krbtgt/REALM.NAME yourself.117* <p>118* Note that you can safely add a principal at any time after the KDC is119* started and before a user requests info on this principal.120* <p>121* 3. Other public methods:122* <ul>123* <li> <code>getPort</code>: Returns the port number the KDC uses124* <li> <code>getRealm</code>: Returns the realm name125* <li> <code>writeKtab</code>: Writes all principals' keys into a keytab file126* <li> <code>saveConfig</code>: Saves a krb5.conf file to access this KDC127* <li> <code>setOption</code>: Sets various options128* </ul>129* Read the javadoc for details. Lazy developer can use <code>OneKDC</code>130* directly.131*/132public class KDC {133134public static final int DEFAULT_LIFETIME = 39600;135public static final int DEFAULT_RENEWTIME = 86400;136137// What etypes the KDC supports. Comma-separated strings. Null for all.138// Please note native KDCs might use different names.139private static final String SUPPORTED_ETYPES140= System.getProperty("kdc.supported.enctypes");141142// The native KDC143private final NativeKdc nativeKdc;144145// The native KDC process146private Process kdcProc = null;147148// Under the hood.149150// Principal db. principal -> pass. A case-insensitive TreeMap is used151// so that even if the client provides a name with different case, the KDC152// can still locate the principal and give back correct salt.153private TreeMap<String,char[]> passwords = new TreeMap<>154(String.CASE_INSENSITIVE_ORDER);155156// Non default salts. Precisely, there should be different salts for157// different etypes, pretend they are the same at the moment.158private TreeMap<String,String> salts = new TreeMap<>159(String.CASE_INSENSITIVE_ORDER);160161// Non default s2kparams for newer etypes. Precisely, there should be162// different s2kparams for different etypes, pretend they are the same163// at the moment.164private TreeMap<String,byte[]> s2kparamses = new TreeMap<>165(String.CASE_INSENSITIVE_ORDER);166167// Alias for referrals.168private TreeMap<String,KDC> aliasReferrals = new TreeMap<>169(String.CASE_INSENSITIVE_ORDER);170171// Alias for local resolution.172private TreeMap<String,PrincipalName> alias2Principals = new TreeMap<>173(String.CASE_INSENSITIVE_ORDER);174175// Realm name176private String realm;177// KDC178private String kdc;179// Service port number180private int port;181// The request/response job queue182private BlockingQueue<Job> q = new ArrayBlockingQueue<>(100);183// Options184private Map<Option,Object> options = new HashMap<>();185// Realm-specific krb5.conf settings186private List<String> conf = new ArrayList<>();187188private Thread thread1, thread2, thread3;189private volatile boolean udpConsumerReady = false;190private volatile boolean tcpConsumerReady = false;191private volatile boolean dispatcherReady = false;192DatagramSocket u1 = null;193ServerSocket t1 = null;194195public static enum KtabMode { APPEND, EXISTING };196197/**198* Option names, to be expanded forever.199*/200public static enum Option {201/**202* Whether pre-authentication is required. Default Boolean.TRUE203*/204PREAUTH_REQUIRED,205/**206* Only issue TGT in RC4207*/208ONLY_RC4_TGT,209/**210* Use RC4 as the first in preauth211*/212RC4_FIRST_PREAUTH,213/**214* Use only one preauth, so that some keys are not easy to generate215*/216ONLY_ONE_PREAUTH,217/**218* Set all name-type to a value in response219*/220RESP_NT,221/**222* Multiple ETYPE-INFO-ENTRY with same etype but different salt223*/224DUP_ETYPE,225/**226* What backend server can be delegated to227*/228OK_AS_DELEGATE,229/**230* Allow S4U2self, List<String> of middle servers.231* If not set, means KDC does not understand S4U2self at all, therefore232* would ignore any PA-FOR-USER request and send a ticket using the233* cname of teh requestor. If set, it returns FORWARDABLE tickets to234* a server with its name in the list235*/236ALLOW_S4U2SELF,237/**238* Allow S4U2proxy, Map<String,List<String>> of middle servers to239* backends. If not set or a backend not in a server's list,240* Krb5.KDC_ERR_POLICY will be send for S4U2proxy request.241*/242ALLOW_S4U2PROXY,243/**244* Sensitive accounts can never be delegated.245*/246SENSITIVE_ACCOUNTS,247/**248* If true, will check if TGS-REQ contains a non-null addresses field.249*/250CHECK_ADDRESSES,251};252253//static {254// System.setProperty("sun.net.spi.nameservice.provider.1", "ns,mock");255//}256257/**258* A standalone KDC server.259*/260public static void main(String[] args) throws Exception {261int port = args.length > 0 ? Integer.parseInt(args[0]) : 0;262KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", port, false);263kdc.addPrincipal("dummy", "bogus".toCharArray());264kdc.addPrincipal("foo", "bar".toCharArray());265kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE");266kdc.addPrincipalRandKey("server/host.rabbit.hole");267kdc.addPrincipalRandKey("backend/host.rabbit.hole");268KDC.saveConfig("krb5.conf", kdc, "forwardable = true");269}270271/**272* Creates and starts a KDC running as a daemon on a random port.273* @param realm the realm name274* @return the running KDC instance275* @throws java.io.IOException for any socket creation error276*/277public static KDC create(String realm) throws IOException {278return create(realm, "kdc." + realm.toLowerCase(), 0, true);279}280281public static KDC existing(String realm, String kdc, int port) {282KDC k = new KDC(realm, kdc);283k.port = port;284return k;285}286287/**288* Creates and starts a KDC server.289* @param realm the realm name290* @param port the TCP and UDP port to listen to. A random port will to291* chosen if zero.292* @param asDaemon if true, KDC threads will be daemons. Otherwise, not.293* @return the running KDC instance294* @throws java.io.IOException for any socket creation error295*/296public static KDC create(String realm, String kdc, int port,297boolean asDaemon) throws IOException {298return new KDC(realm, kdc, port, asDaemon);299}300301/**302* Sets an option303* @param key the option name304* @param value the value305*/306public void setOption(Option key, Object value) {307if (value == null) {308options.remove(key);309} else {310options.put(key, value);311}312}313314/**315* Writes or appends keys into a keytab.316* <p>317* Attention: This is the most basic one of a series of methods below on318* keytab creation or modification. All these methods reference krb5.conf319* settings. If you need to modify krb5.conf or switch to another krb5.conf320* later, please call <code>Config.refresh()</code> again. For example:321* <pre>322* kdc.writeKtab("/etc/kdc/ktab", true); // Config is initialized,323* System.setProperty("java.security.krb5.conf", "/home/mykrb5.conf");324* Config.refresh();325* </pre>326* Inside this method there are 2 places krb5.conf is used:327* <ol>328* <li> (Fatal) Generating keys: EncryptionKey.acquireSecretKeys329* <li> (Has workaround) Creating PrincipalName330* </ol>331* @param tab the keytab file name332* @param append true if append, otherwise, overwrite.333* @param names the names to write into, write all if names is empty334*/335public void writeKtab(String tab, boolean append, String... names)336throws IOException, KrbException {337KeyTab ktab = null;338if (nativeKdc == null) {339ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab);340}341Iterable<String> entries =342(names.length != 0) ? Arrays.asList(names): passwords.keySet();343for (String name : entries) {344if (name.indexOf('@') < 0) {345name = name + "@" + realm;346}347if (nativeKdc == null) {348char[] pass = passwords.get(name);349int kvno = 0;350if (Character.isDigit(pass[pass.length - 1])) {351kvno = pass[pass.length - 1] - '0';352}353PrincipalName pn = new PrincipalName(name,354name.indexOf('/') < 0 ?355PrincipalName.KRB_NT_UNKNOWN :356PrincipalName.KRB_NT_SRV_HST);357ktab.addEntry(pn,358getSalt(pn),359pass,360kvno,361true);362} else {363nativeKdc.ktadd(name, tab);364}365}366if (nativeKdc == null) {367ktab.save();368}369}370371/**372* Writes all principals' keys from multiple KDCs into one keytab file.373* @throws java.io.IOException for any file output error374* @throws sun.security.krb5.KrbException for any realm and/or principal375* name error.376*/377public static void writeMultiKtab(String tab, KDC... kdcs)378throws IOException, KrbException {379KeyTab.create(tab).save(); // Empty the old keytab380appendMultiKtab(tab, kdcs);381}382383/**384* Appends all principals' keys from multiple KDCs to one keytab file.385*/386public static void appendMultiKtab(String tab, KDC... kdcs)387throws IOException, KrbException {388for (KDC kdc: kdcs) {389kdc.writeKtab(tab, true);390}391}392393/**394* Write a ktab for this KDC.395*/396public void writeKtab(String tab) throws IOException, KrbException {397writeKtab(tab, false);398}399400/**401* Appends keys in this KDC to a ktab.402*/403public void appendKtab(String tab) throws IOException, KrbException {404writeKtab(tab, true);405}406407/**408* Adds a new principal to this realm with a given password.409* @param user the principal's name. For a service principal, use the410* form of host/f.q.d.n411* @param pass the password for the principal412*/413public void addPrincipal(String user, char[] pass) {414addPrincipal(user, pass, null, null);415}416417/**418* Adds a new principal to this realm with a given password.419* @param user the principal's name. For a service principal, use the420* form of host/f.q.d.n421* @param pass the password for the principal422* @param salt the salt, or null if a default value will be used423* @param s2kparams the s2kparams, or null if a default value will be used424*/425public void addPrincipal(426String user, char[] pass, String salt, byte[] s2kparams) {427if (user.indexOf('@') < 0) {428user = user + "@" + realm;429}430if (nativeKdc != null) {431if (!user.equals("krbtgt/" + realm)) {432nativeKdc.addPrincipal(user, new String(pass));433}434passwords.put(user, new char[0]);435} else {436passwords.put(user, pass);437if (salt != null) {438salts.put(user, salt);439}440if (s2kparams != null) {441s2kparamses.put(user, s2kparams);442}443}444}445446/**447* Adds a new principal to this realm with a random password448* @param user the principal's name. For a service principal, use the449* form of host/f.q.d.n450*/451public void addPrincipalRandKey(String user) {452addPrincipal(user, randomPassword());453}454455/**456* Returns the name of this realm457* @return the name of this realm458*/459public String getRealm() {460return realm;461}462463/**464* Returns the name of kdc465* @return the name of kdc466*/467public String getKDC() {468return kdc;469}470471/**472* Add realm-specific krb5.conf setting473*/474public void addConf(String s) {475conf.add(s);476}477478/**479* Writes a krb5.conf for one or more KDC that includes KDC locations for480* each realm and the default realm name. You can also add extra strings481* into the file. The method should be called like:482* <pre>483* KDC.saveConfig("krb5.conf", kdc1, kdc2, ..., line1, line2, ...);484* </pre>485* Here you can provide one or more kdc# and zero or more line# arguments.486* The line# will be put after [libdefaults] and before [realms]. Therefore487* you can append new lines into [libdefaults] and/or create your new488* stanzas as well. Note that a newline character will be appended to489* each line# argument.490* <p>491* For example:492* <pre>493* KDC.saveConfig("krb5.conf", this);494* </pre>495* generates:496* <pre>497* [libdefaults]498* default_realm = REALM.NAME499*500* [realms]501* REALM.NAME = {502* kdc = host:port_number503* # realm-specific settings504* }505* </pre>506*507* Another example:508* <pre>509* KDC.saveConfig("krb5.conf", kdc1, kdc2, "forwardable = true", "",510* "[domain_realm]",511* ".kdc1.com = KDC1.NAME");512* </pre>513* generates:514* <pre>515* [libdefaults]516* default_realm = KDC1.NAME517* forwardable = true518*519* [domain_realm]520* .kdc1.com = KDC1.NAME521*522* [realms]523* KDC1.NAME = {524* kdc = host:port1525* }526* KDC2.NAME = {527* kdc = host:port2528* }529* </pre>530* @param file the name of the file to write into531* @param kdc the first (and default) KDC532* @param more more KDCs or extra lines (in their appearing order) to533* insert into the krb5.conf file. This method reads each argument's type534* to determine what it's for. This argument can be empty.535* @throws java.io.IOException for any file output error536*/537public static void saveConfig(String file, KDC kdc, Object... more)538throws IOException {539StringBuffer sb = new StringBuffer();540sb.append("[libdefaults]\ndefault_realm = ");541sb.append(kdc.realm);542sb.append("\n");543for (Object o : more) {544if (o instanceof String) {545sb.append(o);546sb.append("\n");547}548}549sb.append("\n[realms]\n");550sb.append(kdc.realmLine());551for (Object o : more) {552if (o instanceof KDC) {553sb.append(((KDC) o).realmLine());554}555}556Files.write(Paths.get(file), sb.toString().getBytes());557}558559/**560* Returns the service port of the KDC server.561* @return the KDC service port562*/563public int getPort() {564return port;565}566567/**568* Register an alias name to be referred to a different KDC for569* resolution, according to RFC 6806.570* @param alias Alias name (i.e. [email protected]).571* @param referredKDC KDC to which the alias is referred for resolution.572*/573public void registerAlias(String alias, KDC referredKDC) {574aliasReferrals.remove(alias);575aliasReferrals.put(alias, referredKDC);576}577578/**579* Register an alias to be resolved to a Principal Name locally,580* according to RFC 6806.581* @param alias Alias name (i.e. [email protected]).582* @param user Principal Name to which the alias is resolved.583*/584public void registerAlias(String alias, String user)585throws RealmException {586alias2Principals.remove(alias);587alias2Principals.put(alias, new PrincipalName(user));588}589590// Private helper methods591592/**593* Private constructor, cannot be called outside.594* @param realm595*/596private KDC(String realm, String kdc) {597this.realm = realm;598this.kdc = kdc;599this.nativeKdc = null;600}601602/**603* A constructor that starts the KDC service also.604*/605protected KDC(String realm, String kdc, int port, boolean asDaemon)606throws IOException {607this.realm = realm;608this.kdc = kdc;609this.nativeKdc = NativeKdc.get(this);610startServer(port, asDaemon);611}612/**613* Generates a 32-char random password614* @return the password615*/616private static char[] randomPassword() {617char[] pass = new char[32];618Random r = new Random();619for (int i=0; i<31; i++)620pass[i] = (char)('a' + r.nextInt(26));621// The last char cannot be a number, otherwise, keyForUser()622// believes it's a sign of kvno623pass[31] = 'Z';624return pass;625}626627/**628* Generates a random key for the given encryption type.629* @param eType the encryption type630* @return the generated key631* @throws sun.security.krb5.KrbException for unknown/unsupported etype632*/633private static EncryptionKey generateRandomKey(int eType)634throws KrbException {635// Is 32 enough for AES256? I should have generated the keys directly636// but different cryptos have different rules on what keys are valid.637char[] pass = randomPassword();638String algo;639switch (eType) {640case EncryptedData.ETYPE_DES_CBC_MD5: algo = "DES"; break;641case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: algo = "DESede"; break;642case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: algo = "AES128"; break;643case EncryptedData.ETYPE_ARCFOUR_HMAC: algo = "ArcFourHMAC"; break;644case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: algo = "AES256"; break;645default: algo = "DES"; break;646}647return new EncryptionKey(pass, "NOTHING", algo); // Silly648}649650/**651* Returns the password for a given principal652* @param p principal653* @return the password654* @throws sun.security.krb5.KrbException when the principal is not inside655* the database.656*/657private char[] getPassword(PrincipalName p, boolean server)658throws KrbException {659String pn = p.toString();660if (p.getRealmString() == null) {661pn = pn + "@" + getRealm();662}663char[] pass = passwords.get(pn);664if (pass == null) {665throw new KrbException(server?666Krb5.KDC_ERR_S_PRINCIPAL_UNKNOWN:667Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN, pn.toString());668}669return pass;670}671672/**673* Returns the salt string for the principal.674* @param p principal675* @return the salt676*/677protected String getSalt(PrincipalName p) {678String pn = p.toString();679if (p.getRealmString() == null) {680pn = pn + "@" + getRealm();681}682if (salts.containsKey(pn)) {683return salts.get(pn);684}685if (passwords.containsKey(pn)) {686try {687// Find the principal name with correct case.688p = new PrincipalName(passwords.ceilingEntry(pn).getKey());689} catch (RealmException re) {690// Won't happen691}692}693String s = p.getRealmString();694if (s == null) s = getRealm();695for (String n: p.getNameStrings()) {696s += n;697}698return s;699}700701/**702* Returns the s2kparams for the principal given the etype.703* @param p principal704* @param etype encryption type705* @return the s2kparams, might be null706*/707protected byte[] getParams(PrincipalName p, int etype) {708switch (etype) {709case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96:710case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96:711String pn = p.toString();712if (p.getRealmString() == null) {713pn = pn + "@" + getRealm();714}715if (s2kparamses.containsKey(pn)) {716return s2kparamses.get(pn);717}718return new byte[] {0, 0, 0x10, 0};719default:720return null;721}722}723724/**725* Returns the key for a given principal of the given encryption type726* @param p the principal727* @param etype the encryption type728* @param server looking for a server principal?729* @return the key730* @throws sun.security.krb5.KrbException for unknown/unsupported etype731*/732EncryptionKey keyForUser(PrincipalName p, int etype, boolean server)733throws KrbException {734try {735// Do not call EncryptionKey.acquireSecretKeys(), otherwise736// the krb5.conf config file would be loaded.737Integer kvno = null;738// For service whose password ending with a number, use it as kvno.739// Kvno must be postive.740if (p.toString().indexOf('/') > 0) {741char[] pass = getPassword(p, server);742if (Character.isDigit(pass[pass.length-1])) {743kvno = pass[pass.length-1] - '0';744}745}746return new EncryptionKey(EncryptionKeyDotStringToKey(747getPassword(p, server), getSalt(p), getParams(p, etype), etype),748etype, kvno);749} catch (KrbException ke) {750throw ke;751} catch (Exception e) {752throw new RuntimeException(e); // should not happen753}754}755756/**757* Returns a KerberosTime.758*759* @param offset offset from NOW in seconds760*/761private static KerberosTime timeAfter(int offset) {762return new KerberosTime(new Date().getTime() + offset * 1000L);763}764765/**766* Processes an incoming request and generates a response.767* @param in the request768* @return the response769* @throws java.lang.Exception for various errors770*/771protected byte[] processMessage(byte[] in) throws Exception {772if ((in[0] & 0x1f) == Krb5.KRB_AS_REQ)773return processAsReq(in);774else775return processTgsReq(in);776}777778/**779* Processes a TGS_REQ and generates a TGS_REP (or KRB_ERROR)780* @param in the request781* @return the response782* @throws java.lang.Exception for various errors783*/784protected byte[] processTgsReq(byte[] in) throws Exception {785TGSReq tgsReq = new TGSReq(in);786PrincipalName service = tgsReq.reqBody.sname;787if (options.containsKey(KDC.Option.RESP_NT)) {788service = new PrincipalName((int)options.get(KDC.Option.RESP_NT),789service.getNameStrings(), service.getRealm());790}791try {792System.out.println(realm + "> " + tgsReq.reqBody.cname +793" sends TGS-REQ for " +794service + ", " + tgsReq.reqBody.kdcOptions);795KDCReqBody body = tgsReq.reqBody;796int[] eTypes = filterSupported(KDCReqBodyDotEType(body));797if (eTypes.length == 0) {798throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);799}800int e2 = eTypes[0]; // etype for outgoing session key801int e3 = eTypes[0]; // etype for outgoing ticket802803PAData[] pas = tgsReq.pAData;804805Ticket tkt = null;806EncTicketPart etp = null;807808PrincipalName cname = null;809boolean allowForwardable = true;810boolean isReferral = false;811if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) {812System.out.println(realm + "> verifying referral for " +813body.sname.getNameString());814KDC referral = aliasReferrals.get(body.sname.getNameString());815if (referral != null) {816service = new PrincipalName(817PrincipalName.TGS_DEFAULT_SRV_NAME +818PrincipalName.NAME_COMPONENT_SEPARATOR_STR +819referral.getRealm(), PrincipalName.KRB_NT_SRV_INST,820this.getRealm());821System.out.println(realm + "> referral to " +822referral.getRealm());823isReferral = true;824}825}826827if (pas == null || pas.length == 0) {828throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);829} else {830PrincipalName forUserCName = null;831for (PAData pa: pas) {832if (pa.getType() == Krb5.PA_TGS_REQ) {833APReq apReq = new APReq(pa.getValue());834tkt = apReq.ticket;835int te = tkt.encPart.getEType();836EncryptionKey kkey = keyForUser(tkt.sname, te, true);837byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET);838DerInputStream derIn = new DerInputStream(bb);839DerValue der = derIn.getDerValue();840etp = new EncTicketPart(der.toByteArray());841// Finally, cname will be overwritten by PA-FOR-USER842// if it exists.843cname = etp.cname;844System.out.println(realm + "> presenting a ticket of "845+ etp.cname + " to " + tkt.sname);846} else if (pa.getType() == Krb5.PA_FOR_USER) {847if (options.containsKey(Option.ALLOW_S4U2SELF)) {848PAForUserEnc p4u = new PAForUserEnc(849new DerValue(pa.getValue()), null);850forUserCName = p4u.name;851System.out.println(realm + "> See PA_FOR_USER "852+ " in the name of " + p4u.name);853}854}855}856if (forUserCName != null) {857List<String> names = (List<String>)858options.get(Option.ALLOW_S4U2SELF);859if (!names.contains(cname.toString())) {860// Mimic the normal KDC behavior. When a server is not861// allowed to send S4U2self, do not send an error.862// Instead, send a ticket which is useless later.863allowForwardable = false;864}865cname = forUserCName;866}867if (tkt == null) {868throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);869}870}871872// Session key for original ticket, TGT873EncryptionKey ckey = etp.key;874875// Session key for session with the service876EncryptionKey key = generateRandomKey(e2);877878// Check time, TODO879KerberosTime from = body.from;880KerberosTime till = body.till;881if (from == null || from.isZero()) {882from = timeAfter(0);883}884KerberosTime rtime = body.rtime;885if (till == null) {886throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO887} else if (till.isZero()) {888till = timeAfter(DEFAULT_LIFETIME);889}890if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {891rtime = timeAfter(DEFAULT_RENEWTIME);892}893894boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];895if (body.kdcOptions.get(KDCOptions.FORWARDABLE)896&& allowForwardable) {897List<String> sensitives = (List<String>)898options.get(Option.SENSITIVE_ACCOUNTS);899if (sensitives != null && sensitives.contains(cname.toString())) {900// Cannot make FORWARDABLE901} else {902bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true;903}904}905// We do not request for addresses for FORWARDED tickets906if (options.containsKey(Option.CHECK_ADDRESSES)907&& body.kdcOptions.get(KDCOptions.FORWARDED)908&& body.addresses != null) {909throw new KrbException(Krb5.KDC_ERR_BADOPTION);910}911if (body.kdcOptions.get(KDCOptions.FORWARDED) ||912etp.flags.get(Krb5.TKT_OPTS_FORWARDED)) {913bFlags[Krb5.TKT_OPTS_FORWARDED] = true;914}915if (body.kdcOptions.get(KDCOptions.RENEWABLE)) {916bFlags[Krb5.TKT_OPTS_RENEWABLE] = true;917//renew = timeAfter(3600 * 24 * 7);918}919if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {920bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;921}922if (body.kdcOptions.get(KDCOptions.POSTDATED)) {923bFlags[Krb5.TKT_OPTS_POSTDATED] = true;924}925if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) {926bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true;927}928if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT)) {929if (!options.containsKey(Option.ALLOW_S4U2PROXY)) {930// Don't understand CNAME_IN_ADDL_TKT931throw new KrbException(Krb5.KDC_ERR_BADOPTION);932} else {933Map<String,List<String>> map = (Map<String,List<String>>)934options.get(Option.ALLOW_S4U2PROXY);935Ticket second = KDCReqBodyDotFirstAdditionalTicket(body);936EncryptionKey key2 = keyForUser(937second.sname, second.encPart.getEType(), true);938byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET);939DerInputStream derIn = new DerInputStream(bb);940DerValue der = derIn.getDerValue();941EncTicketPart tktEncPart = new EncTicketPart(der.toByteArray());942if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)) {943//throw new KrbException(Krb5.KDC_ERR_BADOPTION);944}945PrincipalName client = tktEncPart.cname;946System.out.println(realm + "> and an additional ticket of "947+ client + " to " + second.sname);948if (map.containsKey(cname.toString())) {949if (map.get(cname.toString()).contains(service.toString())) {950System.out.println(realm + "> S4U2proxy OK");951} else {952throw new KrbException(Krb5.KDC_ERR_BADOPTION);953}954} else {955throw new KrbException(Krb5.KDC_ERR_BADOPTION);956}957cname = client;958}959}960961String okAsDelegate = (String)options.get(Option.OK_AS_DELEGATE);962if (okAsDelegate != null && (963okAsDelegate.isEmpty() ||964okAsDelegate.contains(service.getNameString()))) {965bFlags[Krb5.TKT_OPTS_DELEGATE] = true;966}967bFlags[Krb5.TKT_OPTS_INITIAL] = true;968969KerberosTime renewTill = etp.renewTill;970if (renewTill != null && body.kdcOptions.get(KDCOptions.RENEW)) {971// till should never pass renewTill972if (till.greaterThan(renewTill)) {973till = renewTill;974}975if (System.getProperty("test.set.null.renew") != null) {976// Testing 8186576, see NullRenewUntil.java.977renewTill = null;978}979}980981TicketFlags tFlags = new TicketFlags(bFlags);982EncTicketPart enc = new EncTicketPart(983tFlags,984key,985cname,986new TransitedEncoding(1, new byte[0]), // TODO987timeAfter(0),988from,989till, renewTill,990body.addresses != null ? body.addresses991: etp.caddr,992null);993EncryptionKey skey = keyForUser(service, e3, true);994if (skey == null) {995throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO996}997Ticket t = new Ticket(998System.getProperty("test.kdc.diff.sname") != null ?999new PrincipalName("xx" + service.toString()) :1000service,1001new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET)1002);1003EncTGSRepPart enc_part = new EncTGSRepPart(1004key,1005new LastReq(new LastReqEntry[] {1006new LastReqEntry(0, timeAfter(-10))1007}),1008body.getNonce(), // TODO: detect replay1009timeAfter(3600 * 24),1010// Next 5 and last MUST be same with ticket1011tFlags,1012timeAfter(0),1013from,1014till, renewTill,1015service,1016body.addresses,1017null1018);1019EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(),1020KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY);1021TGSRep tgsRep = new TGSRep(null,1022cname,1023t,1024edata);1025System.out.println(" Return " + tgsRep.cname1026+ " ticket for " + tgsRep.ticket.sname + ", flags "1027+ tFlags);10281029DerOutputStream out = new DerOutputStream();1030out.write(DerValue.createTag(DerValue.TAG_APPLICATION,1031true, (byte)Krb5.KRB_TGS_REP), tgsRep.asn1Encode());1032return out.toByteArray();1033} catch (KrbException ke) {1034ke.printStackTrace(System.out);1035KRBError kerr = ke.getError();1036KDCReqBody body = tgsReq.reqBody;1037System.out.println(" Error " + ke.returnCode()1038+ " " +ke.returnCodeMessage());1039if (kerr == null) {1040kerr = new KRBError(null, null, null,1041timeAfter(0),10420,1043ke.returnCode(),1044body.cname,1045service,1046KrbException.errorMessage(ke.returnCode()),1047null);1048}1049return kerr.asn1Encode();1050}1051}10521053/**1054* Processes a AS_REQ and generates a AS_REP (or KRB_ERROR)1055* @param in the request1056* @return the response1057* @throws java.lang.Exception for various errors1058*/1059protected byte[] processAsReq(byte[] in) throws Exception {1060ASReq asReq = new ASReq(in);1061byte[] asReqbytes = asReq.asn1Encode();1062int[] eTypes = null;1063List<PAData> outPAs = new ArrayList<>();10641065PrincipalName service = asReq.reqBody.sname;1066if (options.containsKey(KDC.Option.RESP_NT)) {1067service = new PrincipalName((int)options.get(KDC.Option.RESP_NT),1068service.getNameStrings(),1069Realm.getDefault());1070}1071try {1072System.out.println(realm + "> " + asReq.reqBody.cname +1073" sends AS-REQ for " +1074service + ", " + asReq.reqBody.kdcOptions);10751076KDCReqBody body = asReq.reqBody;10771078eTypes = filterSupported(KDCReqBodyDotEType(body));1079if (eTypes.length == 0) {1080throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);1081}1082int eType = eTypes[0];10831084if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) {1085PrincipalName principal = alias2Principals.get(1086body.cname.getNameString());1087if (principal != null) {1088body.cname = principal;1089} else {1090KDC referral = aliasReferrals.get(body.cname.getNameString());1091if (referral != null) {1092body.cname = new PrincipalName(1093PrincipalName.TGS_DEFAULT_SRV_NAME,1094PrincipalName.KRB_NT_SRV_INST,1095referral.getRealm());1096throw new KrbException(Krb5.KRB_ERR_WRONG_REALM);1097}1098}1099}11001101EncryptionKey ckey = keyForUser(body.cname, eType, false);1102EncryptionKey skey = keyForUser(service, eType, true);11031104if (options.containsKey(KDC.Option.ONLY_RC4_TGT)) {1105int tgtEType = EncryptedData.ETYPE_ARCFOUR_HMAC;1106boolean found = false;1107for (int i=0; i<eTypes.length; i++) {1108if (eTypes[i] == tgtEType) {1109found = true;1110break;1111}1112}1113if (!found) {1114throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);1115}1116skey = keyForUser(service, tgtEType, true);1117}1118if (ckey == null) {1119throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);1120}1121if (skey == null) {1122throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO1123}11241125// Session key1126EncryptionKey key = generateRandomKey(eType);1127// Check time, TODO1128KerberosTime from = body.from;1129KerberosTime till = body.till;1130KerberosTime rtime = body.rtime;1131if (from == null || from.isZero()) {1132from = timeAfter(0);1133}1134if (till == null) {1135throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO1136} else if (till.isZero()) {1137String ttlsVal = System.getProperty("test.kdc.ttl.value");1138if (ttlsVal != null){1139till = timeAfter(duration(ttlsVal));1140if (till.greaterThan(timeAfter(24 * 3600)) &&1141(System.getProperty("test.kdc.force.till") == null)) {1142till = timeAfter(DEFAULT_LIFETIME);1143body.kdcOptions.set(KDCOptions.RENEWABLE, true);1144}1145} else {1146till = timeAfter(DEFAULT_LIFETIME);1147}1148}11491150if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {1151rtime = timeAfter(DEFAULT_RENEWTIME);1152}11531154//body.from1155boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];1156if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) {1157List<String> sensitives = (List<String>)1158options.get(Option.SENSITIVE_ACCOUNTS);1159if (sensitives != null1160&& sensitives.contains(body.cname.toString())) {1161// Cannot make FORWARDABLE1162} else {1163bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true;1164}1165}1166if (body.kdcOptions.get(KDCOptions.RENEWABLE)) {1167bFlags[Krb5.TKT_OPTS_RENEWABLE] = true;1168//renew = timeAfter(3600 * 24 * 7);1169}1170if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {1171bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;1172}1173if (body.kdcOptions.get(KDCOptions.POSTDATED)) {1174bFlags[Krb5.TKT_OPTS_POSTDATED] = true;1175}1176if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) {1177bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true;1178}1179bFlags[Krb5.TKT_OPTS_INITIAL] = true;1180if (System.getProperty("test.kdc.always.enc.pa.rep") != null) {1181bFlags[Krb5.TKT_OPTS_ENC_PA_REP] = true;1182}11831184// Creating PA-DATA1185DerValue[] pas2 = null, pas = null;1186if (options.containsKey(KDC.Option.DUP_ETYPE)) {1187int n = (Integer)options.get(KDC.Option.DUP_ETYPE);1188switch (n) {1189case 1: // customer's case in 70679741190pas2 = new DerValue[] {1191new DerValue(new ETypeInfo2(1, null, null).asn1Encode()),1192new DerValue(new ETypeInfo2(1, "", null).asn1Encode()),1193new DerValue(new ETypeInfo2(11941, realm, new byte[]{1}).asn1Encode()),1195};1196pas = new DerValue[] {1197new DerValue(new ETypeInfo(1, null).asn1Encode()),1198new DerValue(new ETypeInfo(1, "").asn1Encode()),1199new DerValue(new ETypeInfo(1, realm).asn1Encode()),1200};1201break;1202case 2: // we still reject non-null s2kparams and prefer E2 over E1203pas2 = new DerValue[] {1204new DerValue(new ETypeInfo2(12051, realm, new byte[]{1}).asn1Encode()),1206new DerValue(new ETypeInfo2(1, null, null).asn1Encode()),1207new DerValue(new ETypeInfo2(1, "", null).asn1Encode()),1208};1209pas = new DerValue[] {1210new DerValue(new ETypeInfo(1, realm).asn1Encode()),1211new DerValue(new ETypeInfo(1, null).asn1Encode()),1212new DerValue(new ETypeInfo(1, "").asn1Encode()),1213};1214break;1215case 3: // but only E is wrong1216pas = new DerValue[] {1217new DerValue(new ETypeInfo(1, realm).asn1Encode()),1218new DerValue(new ETypeInfo(1, null).asn1Encode()),1219new DerValue(new ETypeInfo(1, "").asn1Encode()),1220};1221break;1222case 4: // we also ignore rc4-hmac1223pas = new DerValue[] {1224new DerValue(new ETypeInfo(23, "ANYTHING").asn1Encode()),1225new DerValue(new ETypeInfo(1, null).asn1Encode()),1226new DerValue(new ETypeInfo(1, "").asn1Encode()),1227};1228break;1229case 5: // "" should be wrong, but we accept it now1230// See s.s.k.internal.PAData$SaltAndParams1231pas = new DerValue[] {1232new DerValue(new ETypeInfo(1, "").asn1Encode()),1233new DerValue(new ETypeInfo(1, null).asn1Encode()),1234};1235break;1236}1237} else {1238int[] epas = eTypes;1239if (options.containsKey(KDC.Option.RC4_FIRST_PREAUTH)) {1240for (int i=1; i<epas.length; i++) {1241if (epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC) {1242epas[i] = epas[0];1243epas[0] = EncryptedData.ETYPE_ARCFOUR_HMAC;1244break;1245}1246};1247} else if (options.containsKey(KDC.Option.ONLY_ONE_PREAUTH)) {1248epas = new int[] { eTypes[0] };1249}1250pas2 = new DerValue[epas.length];1251for (int i=0; i<epas.length; i++) {1252pas2[i] = new DerValue(new ETypeInfo2(1253epas[i],1254epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ?1255null : getSalt(body.cname),1256getParams(body.cname, epas[i])).asn1Encode());1257}1258boolean allOld = true;1259for (int i: eTypes) {1260if (i == EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96 ||1261i == EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96) {1262allOld = false;1263break;1264}1265}1266if (allOld) {1267pas = new DerValue[epas.length];1268for (int i=0; i<epas.length; i++) {1269pas[i] = new DerValue(new ETypeInfo(1270epas[i],1271epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ?1272null : getSalt(body.cname)1273).asn1Encode());1274}1275}1276}12771278DerOutputStream eid;1279if (pas2 != null) {1280eid = new DerOutputStream();1281eid.putSequence(pas2);1282outPAs.add(new PAData(Krb5.PA_ETYPE_INFO2, eid.toByteArray()));1283}1284if (pas != null) {1285eid = new DerOutputStream();1286eid.putSequence(pas);1287outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray()));1288}12891290PAData[] inPAs = asReq.pAData;1291List<PAData> enc_outPAs = new ArrayList<>();12921293byte[] paEncTimestamp = null;1294if (inPAs != null) {1295for (PAData inPA : inPAs) {1296if (inPA.getType() == Krb5.PA_ENC_TIMESTAMP) {1297paEncTimestamp = inPA.getValue();1298}1299}1300}13011302if (paEncTimestamp == null) {1303Object preauth = options.get(Option.PREAUTH_REQUIRED);1304if (preauth == null || preauth.equals(Boolean.TRUE)) {1305throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED);1306}1307} else {1308EncryptionKey pakey = null;1309try {1310EncryptedData data = newEncryptedData(1311new DerValue(paEncTimestamp));1312pakey = keyForUser(body.cname, data.getEType(), false);1313data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS);1314} catch (Exception e) {1315KrbException ke = new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED);1316ke.initCause(e);1317throw ke;1318}1319bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true;1320for (PAData pa : inPAs) {1321if (pa.getType() == Krb5.PA_REQ_ENC_PA_REP) {1322Checksum ckSum = new Checksum(1323Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128,1324asReqbytes, ckey, KeyUsage.KU_AS_REQ);1325enc_outPAs.add(new PAData(Krb5.PA_REQ_ENC_PA_REP,1326ckSum.asn1Encode()));1327bFlags[Krb5.TKT_OPTS_ENC_PA_REP] = true;1328break;1329}1330}1331}13321333TicketFlags tFlags = new TicketFlags(bFlags);1334EncTicketPart enc = new EncTicketPart(1335tFlags,1336key,1337body.cname,1338new TransitedEncoding(1, new byte[0]),1339timeAfter(0),1340from,1341till, rtime,1342body.addresses,1343null);1344Ticket t = new Ticket(1345service,1346new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET)1347);1348EncASRepPart enc_part = new EncASRepPart(1349key,1350new LastReq(new LastReqEntry[]{1351new LastReqEntry(0, timeAfter(-10))1352}),1353body.getNonce(), // TODO: detect replay?1354timeAfter(3600 * 24),1355// Next 5 and last MUST be same with ticket1356tFlags,1357timeAfter(0),1358from,1359till, rtime,1360service,1361body.addresses,1362enc_outPAs.toArray(new PAData[enc_outPAs.size()])1363);1364EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(),1365KeyUsage.KU_ENC_AS_REP_PART);1366ASRep asRep = new ASRep(1367outPAs.toArray(new PAData[outPAs.size()]),1368body.cname,1369t,1370edata);13711372System.out.println(" Return " + asRep.cname1373+ " ticket for " + asRep.ticket.sname + ", flags "1374+ tFlags);13751376DerOutputStream out = new DerOutputStream();1377out.write(DerValue.createTag(DerValue.TAG_APPLICATION,1378true, (byte)Krb5.KRB_AS_REP), asRep.asn1Encode());1379byte[] result = out.toByteArray();13801381// Added feature:1382// Write the current issuing TGT into a ccache file specified1383// by the system property below.1384String ccache = System.getProperty("test.kdc.save.ccache");1385if (ccache != null) {1386asRep.encKDCRepPart = enc_part;1387sun.security.krb5.internal.ccache.Credentials credentials =1388new sun.security.krb5.internal.ccache.Credentials(asRep);1389CredentialsCache cache =1390CredentialsCache.create(asReq.reqBody.cname, ccache);1391if (cache == null) {1392throw new IOException("Unable to create the cache file " +1393ccache);1394}1395cache.update(credentials);1396cache.save();1397}13981399return result;1400} catch (KrbException ke) {1401ke.printStackTrace(System.out);1402KRBError kerr = ke.getError();1403KDCReqBody body = asReq.reqBody;1404System.out.println(" Error " + ke.returnCode()1405+ " " +ke.returnCodeMessage());1406byte[] eData = null;1407if (kerr == null) {1408if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED ||1409ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) {1410outPAs.add(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]));1411}1412if (outPAs.size() > 0) {1413DerOutputStream bytes = new DerOutputStream();1414for (PAData p: outPAs) {1415bytes.write(p.asn1Encode());1416}1417DerOutputStream temp = new DerOutputStream();1418temp.write(DerValue.tag_Sequence, bytes);1419eData = temp.toByteArray();1420}1421kerr = new KRBError(null, null, null,1422timeAfter(0),14230,1424ke.returnCode(),1425body.cname,1426service,1427KrbException.errorMessage(ke.returnCode()),1428eData);1429}1430return kerr.asn1Encode();1431}1432}14331434/**1435* Translates a duration value into seconds.1436*1437* The format can be one of "h:m[:s]", "NdNhNmNs", and "N". See1438* http://web.mit.edu/kerberos/krb5-devel/doc/basic/date_format.html#duration1439* for definitions.1440*1441* @param s the string duration1442* @return time in seconds1443* @throw KrbException if format is illegal1444*/1445public static int duration(String s) throws KrbException {14461447if (s.isEmpty()) {1448throw new KrbException("Duration cannot be empty");1449}14501451// N1452if (s.matches("\\d+")) {1453return Integer.parseInt(s);1454}14551456// h:m[:s]1457Matcher m = Pattern.compile("(\\d+):(\\d+)(:(\\d+))?").matcher(s);1458if (m.matches()) {1459int hr = Integer.parseInt(m.group(1));1460int min = Integer.parseInt(m.group(2));1461if (min >= 60) {1462throw new KrbException("Illegal duration format " + s);1463}1464int result = hr * 3600 + min * 60;1465if (m.group(4) != null) {1466int sec = Integer.parseInt(m.group(4));1467if (sec >= 60) {1468throw new KrbException("Illegal duration format " + s);1469}1470result += sec;1471}1472return result;1473}14741475// NdNhNmNs1476// 120m allowed. Maybe 1h120m is not good, but still allowed1477m = Pattern.compile(1478"((\\d+)d)?\\s*((\\d+)h)?\\s*((\\d+)m)?\\s*((\\d+)s)?",1479Pattern.CASE_INSENSITIVE).matcher(s);1480if (m.matches()) {1481int result = 0;1482if (m.group(2) != null) {1483result += 86400 * Integer.parseInt(m.group(2));1484}1485if (m.group(4) != null) {1486result += 3600 * Integer.parseInt(m.group(4));1487}1488if (m.group(6) != null) {1489result += 60 * Integer.parseInt(m.group(6));1490}1491if (m.group(8) != null) {1492result += Integer.parseInt(m.group(8));1493}1494return result;1495}14961497throw new KrbException("Illegal duration format " + s);1498}14991500private int[] filterSupported(int[] input) {1501int count = 0;1502for (int i = 0; i < input.length; i++) {1503if (!EType.isSupported(input[i])) {1504continue;1505}1506if (SUPPORTED_ETYPES != null) {1507boolean supported = false;1508for (String se : SUPPORTED_ETYPES.split(",")) {1509if (Config.getType(se) == input[i]) {1510supported = true;1511break;1512}1513}1514if (!supported) {1515continue;1516}1517}1518if (count != i) {1519input[count] = input[i];1520}1521count++;1522}1523if (count != input.length) {1524input = Arrays.copyOf(input, count);1525}1526return input;1527}15281529/**1530* Generates a line for a KDC to put inside [realms] of krb5.conf1531* @return REALM.NAME = { kdc = host:port etc }1532*/1533private String realmLine() {1534StringBuilder sb = new StringBuilder();1535sb.append(realm).append(" = {\n kdc = ")1536.append(kdc).append(':').append(port).append('\n');1537for (String s: conf) {1538sb.append(" ").append(s).append('\n');1539}1540return sb.append("}\n").toString();1541}15421543/**1544* Start the KDC service. This server listens on both UDP and TCP using1545* the same port number. It uses three threads to deal with requests.1546* They can be set to daemon threads if requested.1547* @param port the port number to listen to. If zero, a random available1548* port no less than 8000 will be chosen and used.1549* @param asDaemon true if the KDC threads should be daemons1550* @throws java.io.IOException for any communication error1551*/1552protected void startServer(int port, boolean asDaemon) throws IOException {1553if (nativeKdc != null) {1554startNativeServer(port, asDaemon);1555} else {1556startJavaServer(port, asDaemon);1557}1558}15591560private void startNativeServer(int port, boolean asDaemon) throws IOException {1561nativeKdc.prepare();1562nativeKdc.init();1563kdcProc = nativeKdc.kdc();1564}15651566private void startJavaServer(int port, boolean asDaemon) throws IOException {1567if (port > 0) {1568u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1"));1569t1 = new ServerSocket(port);1570} else {1571while (true) {1572// Try to find a port number that's both TCP and UDP free1573try {1574port = 8000 + new java.util.Random().nextInt(10000);1575u1 = null;1576u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1"));1577t1 = new ServerSocket(port);1578break;1579} catch (Exception e) {1580if (u1 != null) u1.close();1581}1582}1583}1584final DatagramSocket udp = u1;1585final ServerSocket tcp = t1;1586System.out.println("Start KDC on " + port);15871588this.port = port;15891590// The UDP consumer1591thread1 = new Thread() {1592public void run() {1593udpConsumerReady = true;1594while (true) {1595try {1596byte[] inbuf = new byte[8192];1597DatagramPacket p = new DatagramPacket(inbuf, inbuf.length);1598udp.receive(p);1599System.out.println("-----------------------------------------------");1600System.out.println(">>>>> UDP packet received");1601q.put(new Job(processMessage(Arrays.copyOf(inbuf, p.getLength())), udp, p));1602} catch (Exception e) {1603e.printStackTrace();1604}1605}1606}1607};1608thread1.setDaemon(asDaemon);1609thread1.start();16101611// The TCP consumer1612thread2 = new Thread() {1613public void run() {1614tcpConsumerReady = true;1615while (true) {1616try {1617Socket socket = tcp.accept();1618System.out.println("-----------------------------------------------");1619System.out.println(">>>>> TCP connection established");1620DataInputStream in = new DataInputStream(socket.getInputStream());1621DataOutputStream out = new DataOutputStream(socket.getOutputStream());1622byte[] token = new byte[in.readInt()];1623in.readFully(token);1624q.put(new Job(processMessage(token), socket, out));1625} catch (Exception e) {1626e.printStackTrace();1627}1628}1629}1630};1631thread2.setDaemon(asDaemon);1632thread2.start();16331634// The dispatcher1635thread3 = new Thread() {1636public void run() {1637dispatcherReady = true;1638while (true) {1639try {1640q.take().send();1641} catch (Exception e) {1642}1643}1644}1645};1646thread3.setDaemon(true);1647thread3.start();16481649// wait for the KDC is ready1650try {1651while (!isReady()) {1652Thread.sleep(100);1653}1654} catch(InterruptedException e) {1655throw new IOException(e);1656}1657}16581659public void kinit(String user, String ccache) throws Exception {1660if (user.indexOf('@') < 0) {1661user = user + "@" + realm;1662}1663if (nativeKdc != null) {1664nativeKdc.kinit(user, ccache);1665} else {1666Context.fromUserPass(user, passwords.get(user), false)1667.ccache(ccache);1668}1669}16701671boolean isReady() {1672return udpConsumerReady && tcpConsumerReady && dispatcherReady;1673}16741675public void terminate() {1676if (nativeKdc != null) {1677System.out.println("Killing kdc...");1678kdcProc.destroyForcibly();1679System.out.println("Done");1680} else {1681try {1682thread1.stop();1683thread2.stop();1684thread3.stop();1685u1.close();1686t1.close();1687} catch (Exception e) {1688// OK1689}1690}1691}16921693public static KDC startKDC(final String host, final String krbConfFileName,1694final String realm, final Map<String, String> principals,1695final String ktab, final KtabMode mode) {16961697KDC kdc;1698try {1699kdc = KDC.create(realm, host, 0, true);1700kdc.setOption(KDC.Option.PREAUTH_REQUIRED, Boolean.FALSE);1701if (krbConfFileName != null) {1702KDC.saveConfig(krbConfFileName, kdc);1703}17041705// Add principals1706if (principals != null) {1707principals.forEach((name, password) -> {1708if (password == null || password.isEmpty()) {1709System.out.println(String.format(1710"KDC:add a principal '%s' with a random " +1711"password", name));1712kdc.addPrincipalRandKey(name);1713} else {1714System.out.println(String.format(1715"KDC:add a principal '%s' with '%s' password",1716name, password));1717kdc.addPrincipal(name, password.toCharArray());1718}1719});1720}17211722// Create or append keys to existing keytab file1723if (ktab != null) {1724File ktabFile = new File(ktab);1725switch(mode) {1726case APPEND:1727if (ktabFile.exists()) {1728System.out.println(String.format(1729"KDC:append keys to an exising keytab "1730+ "file %s", ktab));1731kdc.appendKtab(ktab);1732} else {1733System.out.println(String.format(1734"KDC:create a new keytab file %s", ktab));1735kdc.writeKtab(ktab);1736}1737break;1738case EXISTING:1739System.out.println(String.format(1740"KDC:use an existing keytab file %s", ktab));1741break;1742default:1743throw new RuntimeException(String.format(1744"KDC:unsupported keytab mode: %s", mode));1745}1746}17471748System.out.println(String.format(1749"KDC: started on %s:%s with '%s' realm",1750host, kdc.getPort(), realm));1751} catch (Exception e) {1752throw new RuntimeException("KDC: unexpected exception", e);1753}17541755return kdc;1756}17571758/**1759* Helper class to encapsulate a job in a KDC.1760*/1761private static class Job {1762byte[] token; // The received request at creation time and1763// the response at send time1764Socket s; // The TCP socket from where the request comes1765DataOutputStream out; // The OutputStream of the TCP socket1766DatagramSocket s2; // The UDP socket from where the request comes1767DatagramPacket dp; // The incoming UDP datagram packet1768boolean useTCP; // Whether TCP or UDP is used17691770// Creates a job object for TCP1771Job(byte[] token, Socket s, DataOutputStream out) {1772useTCP = true;1773this.token = token;1774this.s = s;1775this.out = out;1776}17771778// Creates a job object for UDP1779Job(byte[] token, DatagramSocket s2, DatagramPacket dp) {1780useTCP = false;1781this.token = token;1782this.s2 = s2;1783this.dp = dp;1784}17851786// Sends the output back to the client1787void send() {1788try {1789if (useTCP) {1790System.out.println(">>>>> TCP request honored");1791out.writeInt(token.length);1792out.write(token);1793s.close();1794} else {1795System.out.println(">>>>> UDP request honored");1796s2.send(new DatagramPacket(token, token.length, dp.getAddress(), dp.getPort()));1797}1798} catch (Exception e) {1799e.printStackTrace();1800}1801}1802}18031804public static class KDCNameService implements NameServiceDescriptor {18051806public static String NOT_EXISTING_HOST = "not.existing.host";18071808@Override1809public NameService createNameService() throws Exception {1810NameService ns = new NameService() {1811@Override1812public InetAddress[] lookupAllHostAddr(String host)1813throws UnknownHostException {1814// Everything is localhost except NOT_EXISTING_HOST1815if (NOT_EXISTING_HOST.equals(host)) {1816throw new UnknownHostException("Unknown host name: "1817+ NOT_EXISTING_HOST);1818}1819return new InetAddress[]{1820InetAddress.getByAddress(host, new byte[]{127,0,0,1})1821};1822}1823@Override1824public String getHostByAddr(byte[] addr)1825throws UnknownHostException {1826// No reverse lookup, PrincipalName use original string1827throw new UnknownHostException();1828}1829};1830return ns;1831}18321833@Override1834public String getProviderName() {1835return "mock";1836}18371838@Override1839public String getType() {1840return "ns";1841}1842}18431844/**1845* A native KDC using the binaries in nativePath. Attention:1846* this is using binaries, not an existing KDC instance.1847* An implementation of this takes care of configuration,1848* principal db managing and KDC startup.1849*/1850static abstract class NativeKdc {18511852protected Map<String,String> env;1853protected String nativePath;1854protected String base;1855protected String realm;1856protected int port;18571858NativeKdc(String nativePath, KDC kdc) {1859if (kdc.port == 0) {1860kdc.port = 8000 + new java.util.Random().nextInt(10000);1861}1862this.nativePath = nativePath;1863this.realm = kdc.realm;1864this.port = kdc.port;1865this.base = Paths.get("" + port).toAbsolutePath().toString();1866}18671868// Add a new principal1869abstract void addPrincipal(String user, String pass);1870// Add a keytab entry1871abstract void ktadd(String user, String ktab);1872// Initialize KDC1873abstract void init();1874// Start kdc1875abstract Process kdc();1876// Configuration1877abstract void prepare();1878// Fill ccache1879abstract void kinit(String user, String ccache);18801881static NativeKdc get(KDC kdc) {1882String prop = System.getProperty("native.kdc.path");1883if (prop == null) {1884return null;1885} else if (Files.exists(Paths.get(prop, "sbin/krb5kdc"))) {1886return new MIT(true, prop, kdc);1887} else if (Files.exists(Paths.get(prop, "kdc/krb5kdc"))) {1888return new MIT(false, prop, kdc);1889} else if (Files.exists(Paths.get(prop, "libexec/kdc"))) {1890return new Heimdal(prop, kdc);1891} else {1892throw new IllegalArgumentException("Strange " + prop);1893}1894}18951896Process run(boolean wait, String... cmd) {1897try {1898System.out.println("Running " + cmd2str(env, cmd));1899ProcessBuilder pb = new ProcessBuilder();1900pb.inheritIO();1901pb.environment().putAll(env);1902Process p = pb.command(cmd).start();1903if (wait) {1904if (p.waitFor() < 0) {1905throw new RuntimeException("exit code is not null");1906}1907return null;1908} else {1909return p;1910}1911} catch (Exception e) {1912throw new RuntimeException(e);1913}1914}19151916private String cmd2str(Map<String,String> env, String... cmd) {1917return env.entrySet().stream().map(e -> e.getKey()+"="+e.getValue())1918.collect(Collectors.joining(" ")) + " " +1919Stream.of(cmd).collect(Collectors.joining(" "));1920}1921}19221923// Heimdal KDC. Build your own and run "make install" to nativePath.1924static class Heimdal extends NativeKdc {19251926Heimdal(String nativePath, KDC kdc) {1927super(nativePath, kdc);1928Map<String, String> environment = new HashMap<>();1929environment.put("KRB5_CONFIG", base + "/krb5.conf");1930environment.put("KRB5_TRACE", "/dev/stderr");1931environment.put("DYLD_LIBRARY_PATH", nativePath + "/lib");1932environment.put("LD_LIBRARY_PATH", nativePath + "/lib");1933this.env = Collections.unmodifiableMap(environment);1934}19351936@Override1937public void addPrincipal(String user, String pass) {1938run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,1939"add", "-p", pass, "--use-defaults", user);1940}19411942@Override1943public void ktadd(String user, String ktab) {1944run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,1945"ext_keytab", "-k", ktab, user);1946}19471948@Override1949public void init() {1950run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,1951"init", "--realm-max-ticket-life=1day",1952"--realm-max-renewable-life=1month", realm);1953}19541955@Override1956public Process kdc() {1957return run(false, nativePath + "/libexec/kdc",1958"--addresses=127.0.0.1", "-P", "" + port);1959}19601961@Override1962public void prepare() {1963try {1964Files.createDirectory(Paths.get(base));1965Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList(1966"[libdefaults]",1967"default_realm = " + realm,1968"default_keytab_name = FILE:" + base + "/krb5.keytab",1969"forwardable = true",1970"dns_lookup_kdc = no",1971"dns_lookup_realm = no",1972"dns_canonicalize_hostname = false",1973"\n[realms]",1974realm + " = {",1975" kdc = localhost:" + port,1976"}",1977"\n[kdc]",1978"db-dir = " + base,1979"database = {",1980" label = {",1981" dbname = " + base + "/current-db",1982" realm = " + realm,1983" mkey_file = " + base + "/mkey.file",1984" acl_file = " + base + "/heimdal.acl",1985" log_file = " + base + "/current.log",1986" }",1987"}",1988SUPPORTED_ETYPES == null ? ""1989: ("\n[kadmin]\ndefault_keys = "1990+ (SUPPORTED_ETYPES + ",")1991.replaceAll(",", ":pw-salt ")),1992"\n[logging]",1993"kdc = 0-/FILE:" + base + "/messages.log",1994"krb5 = 0-/FILE:" + base + "/messages.log",1995"default = 0-/FILE:" + base + "/messages.log"1996));1997} catch (IOException e) {1998throw new UncheckedIOException(e);1999}2000}20012002@Override2003void kinit(String user, String ccache) {2004String tmpName = base + "/" + user + "." +2005System.identityHashCode(this) + ".keytab";2006ktadd(user, tmpName);2007run(true, nativePath + "/bin/kinit",2008"-f", "-t", tmpName, "-c", ccache, user);2009}2010}20112012// MIT krb5 KDC. Make your own exploded (install == false), or2013// "make install" into nativePath (install == true).2014static class MIT extends NativeKdc {20152016private boolean install; // "make install" or "make"20172018MIT(boolean install, String nativePath, KDC kdc) {2019super(nativePath, kdc);2020this.install = install;2021Map<String, String> environment = new HashMap<>();2022environment.put("KRB5_KDC_PROFILE", base + "/kdc.conf");2023environment.put("KRB5_CONFIG", base + "/krb5.conf");2024environment.put("KRB5_TRACE", "/dev/stderr");2025environment.put("DYLD_LIBRARY_PATH", nativePath + "/lib");2026environment.put("LD_LIBRARY_PATH", nativePath + "/lib");2027this.env = Collections.unmodifiableMap(environment);2028}20292030@Override2031public void addPrincipal(String user, String pass) {2032run(true, nativePath +2033(install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local",2034"-q", "addprinc -pw " + pass + " " + user);2035}20362037@Override2038public void ktadd(String user, String ktab) {2039run(true, nativePath +2040(install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local",2041"-q", "ktadd -k " + ktab + " -norandkey " + user);2042}20432044@Override2045public void init() {2046run(true, nativePath +2047(install ? "/sbin/" : "/kadmin/dbutil/") + "kdb5_util",2048"create", "-s", "-W", "-P", "olala");2049}20502051@Override2052public Process kdc() {2053return run(false, nativePath +2054(install ? "/sbin/" : "/kdc/") + "krb5kdc",2055"-n");2056}20572058@Override2059public void prepare() {2060try {2061Files.createDirectory(Paths.get(base));2062Files.write(Paths.get(base + "/kdc.conf"), Arrays.asList(2063"[kdcdefaults]",2064"\n[realms]",2065realm + "= {",2066" kdc_listen = " + this.port,2067" kdc_tcp_listen = " + this.port,2068" database_name = " + base + "/principal",2069" key_stash_file = " + base + "/.k5.ATHENA.MIT.EDU",2070SUPPORTED_ETYPES == null ? ""2071: (" supported_enctypes = "2072+ (SUPPORTED_ETYPES + ",")2073.replaceAll(",", ":normal ")),2074"}"2075));2076Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList(2077"[libdefaults]",2078"default_realm = " + realm,2079"default_keytab_name = FILE:" + base + "/krb5.keytab",2080"forwardable = true",2081"dns_lookup_kdc = no",2082"dns_lookup_realm = no",2083"dns_canonicalize_hostname = false",2084"\n[realms]",2085realm + " = {",2086" kdc = localhost:" + port,2087"}",2088"\n[logging]",2089"kdc = FILE:" + base + "/krb5kdc.log"2090));2091} catch (IOException e) {2092throw new UncheckedIOException(e);2093}2094}20952096@Override2097void kinit(String user, String ccache) {2098String tmpName = base + "/" + user + "." +2099System.identityHashCode(this) + ".keytab";2100ktadd(user, tmpName);2101run(true, nativePath +2102(install ? "/bin/" : "/clients/kinit/") + "kinit",2103"-f", "-t", tmpName, "-c", ccache, user);2104}2105}21062107// Calling private methods thru reflections2108private static final Field getEType;2109private static final Constructor<EncryptedData> ctorEncryptedData;2110private static final Method stringToKey;2111private static final Field getAddlTkt;21122113static {2114try {2115ctorEncryptedData = EncryptedData.class.getDeclaredConstructor(DerValue.class);2116ctorEncryptedData.setAccessible(true);2117getEType = KDCReqBody.class.getDeclaredField("eType");2118getEType.setAccessible(true);2119stringToKey = EncryptionKey.class.getDeclaredMethod(2120"stringToKey",2121char[].class, String.class, byte[].class, Integer.TYPE);2122stringToKey.setAccessible(true);2123getAddlTkt = KDCReqBody.class.getDeclaredField("additionalTickets");2124getAddlTkt.setAccessible(true);2125} catch (NoSuchFieldException nsfe) {2126throw new AssertionError(nsfe);2127} catch (NoSuchMethodException nsme) {2128throw new AssertionError(nsme);2129}2130}2131private EncryptedData newEncryptedData(DerValue der) {2132try {2133return ctorEncryptedData.newInstance(der);2134} catch (Exception e) {2135throw new AssertionError(e);2136}2137}2138private static int[] KDCReqBodyDotEType(KDCReqBody body) {2139try {2140return (int[]) getEType.get(body);2141} catch (Exception e) {2142throw new AssertionError(e);2143}2144}2145private static byte[] EncryptionKeyDotStringToKey(char[] password, String salt,2146byte[] s2kparams, int keyType) throws KrbCryptoException {2147try {2148return (byte[])stringToKey.invoke(2149null, password, salt, s2kparams, keyType);2150} catch (InvocationTargetException ex) {2151throw (KrbCryptoException)ex.getCause();2152} catch (Exception e) {2153throw new AssertionError(e);2154}2155}2156private static Ticket KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body) {2157try {2158return ((Ticket[])getAddlTkt.get(body))[0];2159} catch (Exception e) {2160throw new AssertionError(e);2161}2162}2163}216421652166