test/sun/security/krb5/auto/KDC.java

Sun, 31 May 2020 10:13:04 +0800

author
mbalao
date
Sun, 31 May 2020 10:13:04 +0800
changeset 14203
d8bd882cfd2a
parent 13897
9099e882f08e
child 14222
5a272e10d7e7
permissions
-rw-r--r--

8246193: Possible NPE in ENC-PA-REP search in AS-REQ
Reviewed-by: zgu, andrew

weijun@681 1 /*
mbalao@14203 2 * Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.
weijun@681 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
weijun@681 4 *
weijun@681 5 * This code is free software; you can redistribute it and/or modify it
weijun@681 6 * under the terms of the GNU General Public License version 2 only, as
weijun@681 7 * published by the Free Software Foundation.
weijun@681 8 *
weijun@681 9 * This code is distributed in the hope that it will be useful, but WITHOUT
weijun@681 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
weijun@681 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
weijun@681 12 * version 2 for more details (a copy is included in the LICENSE file that
weijun@681 13 * accompanied this code).
weijun@681 14 *
weijun@681 15 * You should have received a copy of the GNU General Public License version
weijun@681 16 * 2 along with this work; if not, write to the Free Software Foundation,
weijun@681 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
weijun@681 18 *
ohair@2365 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
ohair@2365 20 * or visit www.oracle.com if you need additional information or have any
ohair@2365 21 * questions.
weijun@681 22 */
weijun@681 23
weijun@681 24 import java.lang.reflect.Constructor;
weijun@681 25 import java.lang.reflect.Field;
weijun@681 26 import java.lang.reflect.InvocationTargetException;
weijun@681 27 import java.net.*;
weijun@681 28 import java.io.*;
weijun@681 29 import java.lang.reflect.Method;
andrew@13817 30 import java.nio.file.Files;
andrew@13817 31 import java.nio.file.Paths;
weijun@681 32 import java.util.*;
weijun@681 33 import java.util.concurrent.*;
andrew@13817 34 import java.util.stream.Collectors;
andrew@13817 35 import java.util.stream.Stream;
weijun@11706 36
weijun@1303 37 import sun.net.spi.nameservice.NameService;
weijun@1303 38 import sun.net.spi.nameservice.NameServiceDescriptor;
weijun@681 39 import sun.security.krb5.*;
weijun@681 40 import sun.security.krb5.internal.*;
weijun@707 41 import sun.security.krb5.internal.ccache.CredentialsCache;
andrew@13814 42 import sun.security.krb5.internal.crypto.EType;
weijun@681 43 import sun.security.krb5.internal.crypto.KeyUsage;
weijun@681 44 import sun.security.krb5.internal.ktab.KeyTab;
weijun@681 45 import sun.security.util.DerInputStream;
weijun@681 46 import sun.security.util.DerOutputStream;
weijun@681 47 import sun.security.util.DerValue;
pkoppula@13316 48 import java.util.regex.Matcher;
pkoppula@13316 49 import java.util.regex.Pattern;
weijun@681 50
weijun@681 51 /**
weijun@681 52 * A KDC server.
andrew@13817 53 *
andrew@13817 54 * Note: By setting the system property native.kdc.path to a native
andrew@13817 55 * krb5 installation, this class starts a native KDC with the
andrew@13817 56 * given realm and host. It can also add new principals and save keytabs.
andrew@13817 57 * Other features might not be available.
weijun@681 58 * <p>
weijun@681 59 * Features:
weijun@681 60 * <ol>
weijun@681 61 * <li> Supports TCP and UDP
weijun@681 62 * <li> Supports AS-REQ and TGS-REQ
weijun@681 63 * <li> Principal db and other settings hard coded in application
weijun@681 64 * <li> Options, say, request preauth or not
weijun@681 65 * </ol>
weijun@681 66 * Side effects:
weijun@681 67 * <ol>
weijun@681 68 * <li> The Sun-internal class <code>sun.security.krb5.Config</code> is a
weijun@681 69 * singleton and initialized according to Kerberos settings (krb5.conf and
weijun@681 70 * java.security.krb5.* system properties). This means once it's initialized
weijun@681 71 * it will not automatically notice any changes to these settings (or file
weijun@681 72 * changes of krb5.conf). The KDC class normally does not touch these
weijun@681 73 * settings (except for the <code>writeKtab()</code> method). However, to make
weijun@681 74 * sure nothing ever goes wrong, if you want to make any changes to these
weijun@681 75 * settings after calling a KDC method, call <code>Config.refresh()</code> to
weijun@681 76 * make sure your changes are reflected in the <code>Config</code> object.
weijun@681 77 * </ol>
weijun@1944 78 * System properties recognized:
weijun@1944 79 * <ul>
weijun@1944 80 * <li>test.kdc.save.ccache
weijun@1944 81 * </ul>
weijun@681 82 * Issues and TODOs:
weijun@681 83 * <ol>
weijun@681 84 * <li> Generates krb5.conf to be used on another machine, currently the kdc is
weijun@681 85 * always localhost
weijun@681 86 * <li> More options to KDC, say, error output, say, response nonce !=
weijun@681 87 * request nonce
weijun@681 88 * </ol>
weijun@681 89 * Note: This program uses internal krb5 classes (including reflection to
weijun@681 90 * access private fields and methods).
weijun@681 91 * <p>
weijun@681 92 * Usages:
weijun@681 93 * <p>
weijun@681 94 * 1. Init and start the KDC:
weijun@681 95 * <pre>
weijun@681 96 * KDC kdc = KDC.create("REALM.NAME", port, isDaemon);
weijun@681 97 * KDC kdc = KDC.create("REALM.NAME");
weijun@681 98 * </pre>
weijun@681 99 * Here, <code>port</code> is the UDP and TCP port number the KDC server
weijun@681 100 * listens on. If zero, a random port is chosen, which you can use getPort()
weijun@681 101 * later to retrieve the value.
weijun@681 102 * <p>
weijun@681 103 * If <code>isDaemon</code> is true, the KDC worker threads will be daemons.
weijun@681 104 * <p>
weijun@681 105 * The shortcut <code>KDC.create("REALM.NAME")</code> has port=0 and
weijun@681 106 * isDaemon=false, and is commonly used in an embedded KDC.
weijun@681 107 * <p>
weijun@681 108 * 2. Adding users:
weijun@681 109 * <pre>
weijun@681 110 * kdc.addPrincipal(String principal_name, char[] password);
weijun@681 111 * kdc.addPrincipalRandKey(String principal_name);
weijun@681 112 * </pre>
weijun@681 113 * A service principal's name should look like "host/f.q.d.n". The second form
weijun@681 114 * generates a random key. To expose this key, call <code>writeKtab()</code> to
weijun@681 115 * save the keys into a keytab file.
weijun@681 116 * <p>
weijun@681 117 * Note that you need to add the principal name krbtgt/REALM.NAME yourself.
weijun@681 118 * <p>
weijun@681 119 * Note that you can safely add a principal at any time after the KDC is
weijun@681 120 * started and before a user requests info on this principal.
weijun@681 121 * <p>
weijun@681 122 * 3. Other public methods:
weijun@681 123 * <ul>
weijun@681 124 * <li> <code>getPort</code>: Returns the port number the KDC uses
weijun@681 125 * <li> <code>getRealm</code>: Returns the realm name
weijun@681 126 * <li> <code>writeKtab</code>: Writes all principals' keys into a keytab file
weijun@681 127 * <li> <code>saveConfig</code>: Saves a krb5.conf file to access this KDC
weijun@681 128 * <li> <code>setOption</code>: Sets various options
weijun@681 129 * </ul>
weijun@681 130 * Read the javadoc for details. Lazy developer can use <code>OneKDC</code>
weijun@681 131 * directly.
weijun@681 132 */
weijun@681 133 public class KDC {
weijun@681 134
pkoppula@13316 135 public static final int DEFAULT_LIFETIME = 39600;
pkoppula@13316 136 public static final int DEFAULT_RENEWTIME = 86400;
pkoppula@13316 137
andrew@13817 138 // What etypes the KDC supports. Comma-separated strings. Null for all.
andrew@13817 139 // Please note native KDCs might use different names.
andrew@13817 140 private static final String SUPPORTED_ETYPES
andrew@13817 141 = System.getProperty("kdc.supported.enctypes");
andrew@13817 142
andrew@13817 143 // The native KDC
andrew@13817 144 private final NativeKdc nativeKdc;
andrew@13817 145
andrew@13817 146 // The native KDC process
andrew@13817 147 private Process kdcProc = null;
andrew@13817 148
andrew@13814 149 // Under the hood.
andrew@13814 150
weijun@3057 151 // Principal db. principal -> pass. A case-insensitive TreeMap is used
weijun@3057 152 // so that even if the client provides a name with different case, the KDC
weijun@3057 153 // can still locate the principal and give back correct salt.
smarks@3391 154 private TreeMap<String,char[]> passwords = new TreeMap<>
weijun@3057 155 (String.CASE_INSENSITIVE_ORDER);
weijun@3057 156
weijun@13813 157 // Non default salts. Precisely, there should be different salts for
weijun@13813 158 // different etypes, pretend they are the same at the moment.
weijun@13813 159 private TreeMap<String,String> salts = new TreeMap<>
weijun@13813 160 (String.CASE_INSENSITIVE_ORDER);
weijun@13813 161
weijun@13813 162 // Non default s2kparams for newer etypes. Precisely, there should be
weijun@13813 163 // different s2kparams for different etypes, pretend they are the same
weijun@13813 164 // at the moment.
weijun@13813 165 private TreeMap<String,byte[]> s2kparamses = new TreeMap<>
weijun@13813 166 (String.CASE_INSENSITIVE_ORDER);
weijun@13813 167
andrew@13817 168 // Alias for referrals.
andrew@13817 169 private TreeMap<String,KDC> aliasReferrals = new TreeMap<>
andrew@13817 170 (String.CASE_INSENSITIVE_ORDER);
andrew@13817 171
andrew@13817 172 // Alias for local resolution.
andrew@13817 173 private TreeMap<String,PrincipalName> alias2Principals = new TreeMap<>
andrew@13817 174 (String.CASE_INSENSITIVE_ORDER);
andrew@13817 175
weijun@681 176 // Realm name
weijun@681 177 private String realm;
weijun@1303 178 // KDC
weijun@1303 179 private String kdc;
weijun@1303 180 // Service port number
weijun@1303 181 private int port;
weijun@681 182 // The request/response job queue
smarks@3391 183 private BlockingQueue<Job> q = new ArrayBlockingQueue<>(100);
weijun@681 184 // Options
smarks@3391 185 private Map<Option,Object> options = new HashMap<>();
weijun@9656 186 // Realm-specific krb5.conf settings
weijun@9656 187 private List<String> conf = new ArrayList<>();
weijun@681 188
weijun@2037 189 private Thread thread1, thread2, thread3;
msolovie@11701 190 private volatile boolean udpConsumerReady = false;
msolovie@11701 191 private volatile boolean tcpConsumerReady = false;
msolovie@11701 192 private volatile boolean dispatcherReady = false;
weijun@2037 193 DatagramSocket u1 = null;
weijun@2037 194 ServerSocket t1 = null;
weijun@2037 195
weijun@11706 196 public static enum KtabMode { APPEND, EXISTING };
weijun@11706 197
weijun@681 198 /**
weijun@681 199 * Option names, to be expanded forever.
weijun@681 200 */
weijun@681 201 public static enum Option {
weijun@681 202 /**
weijun@681 203 * Whether pre-authentication is required. Default Boolean.TRUE
weijun@681 204 */
weijun@681 205 PREAUTH_REQUIRED,
weijun@2464 206 /**
weijun@2492 207 * Only issue TGT in RC4
weijun@2464 208 */
weijun@2464 209 ONLY_RC4_TGT,
weijun@2492 210 /**
weijun@3057 211 * Use RC4 as the first in preauth
weijun@2492 212 */
weijun@3057 213 RC4_FIRST_PREAUTH,
weijun@3057 214 /**
weijun@3057 215 * Use only one preauth, so that some keys are not easy to generate
weijun@3057 216 */
weijun@3057 217 ONLY_ONE_PREAUTH,
weijun@4446 218 /**
weijun@4446 219 * Set all name-type to a value in response
weijun@4446 220 */
weijun@4446 221 RESP_NT,
weijun@4558 222 /**
weijun@4558 223 * Multiple ETYPE-INFO-ENTRY with same etype but different salt
weijun@4558 224 */
weijun@4558 225 DUP_ETYPE,
weijun@5485 226 /**
weijun@5485 227 * What backend server can be delegated to
weijun@5485 228 */
weijun@5485 229 OK_AS_DELEGATE,
weijun@6093 230 /**
weijun@6093 231 * Allow S4U2self, List<String> of middle servers.
weijun@6093 232 * If not set, means KDC does not understand S4U2self at all, therefore
weijun@6093 233 * would ignore any PA-FOR-USER request and send a ticket using the
weijun@6093 234 * cname of teh requestor. If set, it returns FORWARDABLE tickets to
weijun@6093 235 * a server with its name in the list
weijun@6093 236 */
weijun@6093 237 ALLOW_S4U2SELF,
weijun@6093 238 /**
weijun@6093 239 * Allow S4U2proxy, Map<String,List<String>> of middle servers to
weijun@6093 240 * backends. If not set or a backend not in a server's list,
weijun@6093 241 * Krb5.KDC_ERR_POLICY will be send for S4U2proxy request.
weijun@6093 242 */
weijun@6093 243 ALLOW_S4U2PROXY,
weijun@11700 244 /**
weijun@11700 245 * Sensitive accounts can never be delegated.
weijun@11700 246 */
weijun@11700 247 SENSITIVE_ACCOUNTS,
andrew@13843 248 /**
andrew@13843 249 * If true, will check if TGS-REQ contains a non-null addresses field.
andrew@13843 250 */
andrew@13843 251 CHECK_ADDRESSES,
weijun@681 252 };
weijun@681 253
weijun@12865 254 //static {
weijun@12865 255 // System.setProperty("sun.net.spi.nameservice.provider.1", "ns,mock");
weijun@12865 256 //}
weijun@1303 257
weijun@681 258 /**
weijun@681 259 * A standalone KDC server.
weijun@681 260 */
weijun@681 261 public static void main(String[] args) throws Exception {
andrew@13814 262 int port = args.length > 0 ? Integer.parseInt(args[0]) : 0;
andrew@13814 263 KDC kdc = create("RABBIT.HOLE", "kdc.rabbit.hole", port, false);
weijun@681 264 kdc.addPrincipal("dummy", "bogus".toCharArray());
weijun@681 265 kdc.addPrincipal("foo", "bar".toCharArray());
weijun@1303 266 kdc.addPrincipalRandKey("krbtgt/RABBIT.HOLE");
weijun@1303 267 kdc.addPrincipalRandKey("server/host.rabbit.hole");
weijun@1303 268 kdc.addPrincipalRandKey("backend/host.rabbit.hole");
weijun@1303 269 KDC.saveConfig("krb5.conf", kdc, "forwardable = true");
weijun@681 270 }
weijun@681 271
weijun@681 272 /**
weijun@681 273 * Creates and starts a KDC running as a daemon on a random port.
weijun@681 274 * @param realm the realm name
weijun@681 275 * @return the running KDC instance
weijun@681 276 * @throws java.io.IOException for any socket creation error
weijun@681 277 */
weijun@681 278 public static KDC create(String realm) throws IOException {
weijun@1303 279 return create(realm, "kdc." + realm.toLowerCase(), 0, true);
weijun@681 280 }
weijun@681 281
weijun@3057 282 public static KDC existing(String realm, String kdc, int port) {
weijun@3057 283 KDC k = new KDC(realm, kdc);
weijun@3057 284 k.port = port;
weijun@3057 285 return k;
weijun@3057 286 }
weijun@3057 287
weijun@681 288 /**
weijun@681 289 * Creates and starts a KDC server.
weijun@681 290 * @param realm the realm name
weijun@681 291 * @param port the TCP and UDP port to listen to. A random port will to
weijun@681 292 * chosen if zero.
weijun@681 293 * @param asDaemon if true, KDC threads will be daemons. Otherwise, not.
weijun@681 294 * @return the running KDC instance
weijun@681 295 * @throws java.io.IOException for any socket creation error
weijun@681 296 */
andrew@13817 297 public static KDC create(String realm, String kdc, int port,
andrew@13817 298 boolean asDaemon) throws IOException {
weijun@1303 299 return new KDC(realm, kdc, port, asDaemon);
weijun@681 300 }
weijun@681 301
weijun@681 302 /**
weijun@681 303 * Sets an option
weijun@681 304 * @param key the option name
weijun@9656 305 * @param value the value
weijun@681 306 */
weijun@681 307 public void setOption(Option key, Object value) {
weijun@5485 308 if (value == null) {
weijun@5485 309 options.remove(key);
weijun@5485 310 } else {
weijun@5485 311 options.put(key, value);
weijun@5485 312 }
weijun@681 313 }
weijun@681 314
weijun@681 315 /**
weijun@5177 316 * Writes or appends keys into a keytab.
weijun@5177 317 * <p>
weijun@5177 318 * Attention: This is the most basic one of a series of methods below on
weijun@5177 319 * keytab creation or modification. All these methods reference krb5.conf
weijun@5177 320 * settings. If you need to modify krb5.conf or switch to another krb5.conf
weijun@5177 321 * later, please call <code>Config.refresh()</code> again. For example:
weijun@5177 322 * <pre>
weijun@5177 323 * kdc.writeKtab("/etc/kdc/ktab", true); // Config is initialized,
weijun@5177 324 * System.setProperty("java.security.krb5.conf", "/home/mykrb5.conf");
weijun@5177 325 * Config.refresh();
weijun@5177 326 * </pre>
weijun@5177 327 * Inside this method there are 2 places krb5.conf is used:
weijun@5177 328 * <ol>
weijun@5177 329 * <li> (Fatal) Generating keys: EncryptionKey.acquireSecretKeys
weijun@5177 330 * <li> (Has workaround) Creating PrincipalName
weijun@5177 331 * </ol>
weijun@5177 332 * @param tab the keytab file name
weijun@4105 333 * @param append true if append, otherwise, overwrite.
weijun@5177 334 * @param names the names to write into, write all if names is empty
weijun@4105 335 */
weijun@5177 336 public void writeKtab(String tab, boolean append, String... names)
weijun@4105 337 throws IOException, KrbException {
andrew@13817 338 KeyTab ktab = null;
andrew@13817 339 if (nativeKdc == null) {
andrew@13817 340 ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab);
andrew@13817 341 }
weijun@5177 342 Iterable<String> entries =
weijun@5177 343 (names.length != 0) ? Arrays.asList(names): passwords.keySet();
weijun@5177 344 for (String name : entries) {
andrew@13817 345 if (name.indexOf('@') < 0) {
andrew@13817 346 name = name + "@" + realm;
weijun@4105 347 }
andrew@13817 348 if (nativeKdc == null) {
andrew@13817 349 char[] pass = passwords.get(name);
andrew@13817 350 int kvno = 0;
andrew@13817 351 if (Character.isDigit(pass[pass.length - 1])) {
andrew@13817 352 kvno = pass[pass.length - 1] - '0';
andrew@13817 353 }
andrew@13817 354 PrincipalName pn = new PrincipalName(name,
weijun@6340 355 name.indexOf('/') < 0 ?
andrew@13817 356 PrincipalName.KRB_NT_UNKNOWN :
andrew@13817 357 PrincipalName.KRB_NT_SRV_HST);
andrew@13817 358 ktab.addEntry(pn,
weijun@6340 359 getSalt(pn),
weijun@5177 360 pass,
weijun@5177 361 kvno,
weijun@5177 362 true);
andrew@13817 363 } else {
andrew@13817 364 nativeKdc.ktadd(name, tab);
andrew@13817 365 }
weijun@4105 366 }
andrew@13817 367 if (nativeKdc == null) {
andrew@13817 368 ktab.save();
andrew@13817 369 }
weijun@4105 370 }
weijun@4105 371
weijun@4105 372 /**
weijun@4105 373 * Writes all principals' keys from multiple KDCs into one keytab file.
weijun@681 374 * @throws java.io.IOException for any file output error
weijun@681 375 * @throws sun.security.krb5.KrbException for any realm and/or principal
weijun@681 376 * name error.
weijun@681 377 */
weijun@1269 378 public static void writeMultiKtab(String tab, KDC... kdcs)
weijun@1269 379 throws IOException, KrbException {
weijun@5177 380 KeyTab.create(tab).save(); // Empty the old keytab
weijun@5177 381 appendMultiKtab(tab, kdcs);
weijun@4105 382 }
weijun@4105 383
weijun@4105 384 /**
weijun@4105 385 * Appends all principals' keys from multiple KDCs to one keytab file.
weijun@4105 386 */
weijun@4105 387 public static void appendMultiKtab(String tab, KDC... kdcs)
weijun@4105 388 throws IOException, KrbException {
weijun@5177 389 for (KDC kdc: kdcs) {
weijun@5177 390 kdc.writeKtab(tab, true);
weijun@5177 391 }
weijun@681 392 }
weijun@681 393
weijun@681 394 /**
weijun@1269 395 * Write a ktab for this KDC.
weijun@1269 396 */
weijun@1269 397 public void writeKtab(String tab) throws IOException, KrbException {
weijun@5177 398 writeKtab(tab, false);
weijun@1269 399 }
weijun@1269 400
weijun@1269 401 /**
weijun@4105 402 * Appends keys in this KDC to a ktab.
weijun@4105 403 */
weijun@4105 404 public void appendKtab(String tab) throws IOException, KrbException {
weijun@5177 405 writeKtab(tab, true);
weijun@4105 406 }
weijun@4105 407
weijun@4105 408 /**
weijun@681 409 * Adds a new principal to this realm with a given password.
weijun@681 410 * @param user the principal's name. For a service principal, use the
weijun@681 411 * form of host/f.q.d.n
weijun@681 412 * @param pass the password for the principal
weijun@681 413 */
weijun@681 414 public void addPrincipal(String user, char[] pass) {
weijun@13813 415 addPrincipal(user, pass, null, null);
weijun@13813 416 }
weijun@13813 417
weijun@13813 418 /**
weijun@13813 419 * Adds a new principal to this realm with a given password.
weijun@13813 420 * @param user the principal's name. For a service principal, use the
weijun@13813 421 * form of host/f.q.d.n
weijun@13813 422 * @param pass the password for the principal
weijun@13813 423 * @param salt the salt, or null if a default value will be used
weijun@13813 424 * @param s2kparams the s2kparams, or null if a default value will be used
weijun@13813 425 */
andrew@13817 426 public void addPrincipal(
andrew@13817 427 String user, char[] pass, String salt, byte[] s2kparams) {
weijun@1303 428 if (user.indexOf('@') < 0) {
weijun@1303 429 user = user + "@" + realm;
weijun@1303 430 }
andrew@13817 431 if (nativeKdc != null) {
andrew@13817 432 if (!user.equals("krbtgt/" + realm)) {
andrew@13817 433 nativeKdc.addPrincipal(user, new String(pass));
andrew@13817 434 }
andrew@13817 435 passwords.put(user, new char[0]);
andrew@13817 436 } else {
andrew@13817 437 passwords.put(user, pass);
andrew@13817 438 if (salt != null) {
andrew@13817 439 salts.put(user, salt);
andrew@13817 440 }
andrew@13817 441 if (s2kparams != null) {
andrew@13817 442 s2kparamses.put(user, s2kparams);
andrew@13817 443 }
weijun@13813 444 }
weijun@681 445 }
weijun@681 446
weijun@681 447 /**
weijun@681 448 * Adds a new principal to this realm with a random password
weijun@681 449 * @param user the principal's name. For a service principal, use the
weijun@681 450 * form of host/f.q.d.n
weijun@681 451 */
weijun@681 452 public void addPrincipalRandKey(String user) {
weijun@1303 453 addPrincipal(user, randomPassword());
weijun@681 454 }
weijun@681 455
weijun@681 456 /**
weijun@681 457 * Returns the name of this realm
weijun@681 458 * @return the name of this realm
weijun@681 459 */
weijun@681 460 public String getRealm() {
weijun@681 461 return realm;
weijun@681 462 }
weijun@681 463
weijun@681 464 /**
weijun@1303 465 * Returns the name of kdc
weijun@1303 466 * @return the name of kdc
weijun@1303 467 */
weijun@1303 468 public String getKDC() {
weijun@1303 469 return kdc;
weijun@1303 470 }
weijun@1303 471
weijun@1303 472 /**
weijun@9656 473 * Add realm-specific krb5.conf setting
weijun@9656 474 */
weijun@9656 475 public void addConf(String s) {
weijun@9656 476 conf.add(s);
weijun@9656 477 }
weijun@9656 478
weijun@9656 479 /**
weijun@681 480 * Writes a krb5.conf for one or more KDC that includes KDC locations for
weijun@681 481 * each realm and the default realm name. You can also add extra strings
weijun@681 482 * into the file. The method should be called like:
weijun@681 483 * <pre>
weijun@681 484 * KDC.saveConfig("krb5.conf", kdc1, kdc2, ..., line1, line2, ...);
weijun@681 485 * </pre>
weijun@681 486 * Here you can provide one or more kdc# and zero or more line# arguments.
weijun@681 487 * The line# will be put after [libdefaults] and before [realms]. Therefore
weijun@681 488 * you can append new lines into [libdefaults] and/or create your new
weijun@681 489 * stanzas as well. Note that a newline character will be appended to
weijun@681 490 * each line# argument.
weijun@681 491 * <p>
weijun@681 492 * For example:
weijun@681 493 * <pre>
weijun@681 494 * KDC.saveConfig("krb5.conf", this);
weijun@681 495 * </pre>
weijun@681 496 * generates:
weijun@681 497 * <pre>
weijun@681 498 * [libdefaults]
weijun@681 499 * default_realm = REALM.NAME
weijun@681 500 *
weijun@681 501 * [realms]
weijun@681 502 * REALM.NAME = {
weijun@1303 503 * kdc = host:port_number
weijun@9656 504 * # realm-specific settings
weijun@681 505 * }
weijun@681 506 * </pre>
weijun@681 507 *
weijun@681 508 * Another example:
weijun@681 509 * <pre>
weijun@681 510 * KDC.saveConfig("krb5.conf", kdc1, kdc2, "forwardable = true", "",
weijun@681 511 * "[domain_realm]",
weijun@681 512 * ".kdc1.com = KDC1.NAME");
weijun@681 513 * </pre>
weijun@681 514 * generates:
weijun@681 515 * <pre>
weijun@681 516 * [libdefaults]
weijun@681 517 * default_realm = KDC1.NAME
weijun@681 518 * forwardable = true
weijun@681 519 *
weijun@681 520 * [domain_realm]
weijun@681 521 * .kdc1.com = KDC1.NAME
weijun@681 522 *
weijun@681 523 * [realms]
weijun@681 524 * KDC1.NAME = {
weijun@1303 525 * kdc = host:port1
weijun@681 526 * }
weijun@681 527 * KDC2.NAME = {
weijun@1303 528 * kdc = host:port2
weijun@681 529 * }
weijun@681 530 * </pre>
weijun@681 531 * @param file the name of the file to write into
weijun@681 532 * @param kdc the first (and default) KDC
weijun@681 533 * @param more more KDCs or extra lines (in their appearing order) to
weijun@681 534 * insert into the krb5.conf file. This method reads each argument's type
weijun@681 535 * to determine what it's for. This argument can be empty.
weijun@681 536 * @throws java.io.IOException for any file output error
weijun@681 537 */
weijun@681 538 public static void saveConfig(String file, KDC kdc, Object... more)
weijun@681 539 throws IOException {
weijun@681 540 StringBuffer sb = new StringBuffer();
weijun@681 541 sb.append("[libdefaults]\ndefault_realm = ");
weijun@681 542 sb.append(kdc.realm);
weijun@681 543 sb.append("\n");
andrew@13817 544 for (Object o : more) {
weijun@681 545 if (o instanceof String) {
weijun@681 546 sb.append(o);
weijun@681 547 sb.append("\n");
weijun@681 548 }
weijun@681 549 }
weijun@681 550 sb.append("\n[realms]\n");
weijun@9656 551 sb.append(kdc.realmLine());
andrew@13817 552 for (Object o : more) {
weijun@681 553 if (o instanceof KDC) {
andrew@13817 554 sb.append(((KDC) o).realmLine());
weijun@681 555 }
weijun@681 556 }
andrew@13817 557 Files.write(Paths.get(file), sb.toString().getBytes());
weijun@681 558 }
weijun@681 559
weijun@681 560 /**
weijun@681 561 * Returns the service port of the KDC server.
weijun@681 562 * @return the KDC service port
weijun@681 563 */
weijun@681 564 public int getPort() {
weijun@681 565 return port;
weijun@681 566 }
weijun@681 567
mbalao@13811 568 /**
mbalao@13811 569 * Register an alias name to be referred to a different KDC for
mbalao@13811 570 * resolution, according to RFC 6806.
mbalao@13811 571 * @param alias Alias name (i.e. user@REALM.COM).
mbalao@13811 572 * @param referredKDC KDC to which the alias is referred for resolution.
mbalao@13811 573 */
mbalao@13811 574 public void registerAlias(String alias, KDC referredKDC) {
mbalao@13811 575 aliasReferrals.remove(alias);
mbalao@13811 576 aliasReferrals.put(alias, referredKDC);
mbalao@13811 577 }
mbalao@13811 578
mbalao@13811 579 /**
mbalao@13811 580 * Register an alias to be resolved to a Principal Name locally,
mbalao@13811 581 * according to RFC 6806.
mbalao@13811 582 * @param alias Alias name (i.e. user@REALM.COM).
mbalao@13811 583 * @param user Principal Name to which the alias is resolved.
mbalao@13811 584 */
mbalao@13811 585 public void registerAlias(String alias, String user)
mbalao@13811 586 throws RealmException {
mbalao@13811 587 alias2Principals.remove(alias);
mbalao@13811 588 alias2Principals.put(alias, new PrincipalName(user));
mbalao@13811 589 }
mbalao@13811 590
weijun@681 591 // Private helper methods
weijun@681 592
weijun@681 593 /**
weijun@681 594 * Private constructor, cannot be called outside.
weijun@681 595 * @param realm
weijun@681 596 */
weijun@1303 597 private KDC(String realm, String kdc) {
weijun@681 598 this.realm = realm;
weijun@1303 599 this.kdc = kdc;
andrew@13817 600 this.nativeKdc = null;
weijun@681 601 }
weijun@681 602
weijun@681 603 /**
weijun@681 604 * A constructor that starts the KDC service also.
weijun@681 605 */
weijun@1303 606 protected KDC(String realm, String kdc, int port, boolean asDaemon)
weijun@681 607 throws IOException {
andrew@13817 608 this.realm = realm;
andrew@13817 609 this.kdc = kdc;
andrew@13817 610 this.nativeKdc = NativeKdc.get(this);
weijun@681 611 startServer(port, asDaemon);
weijun@681 612 }
weijun@681 613 /**
weijun@681 614 * Generates a 32-char random password
weijun@681 615 * @return the password
weijun@681 616 */
weijun@681 617 private static char[] randomPassword() {
weijun@681 618 char[] pass = new char[32];
andrew@13817 619 Random r = new Random();
weijun@2416 620 for (int i=0; i<31; i++)
andrew@13817 621 pass[i] = (char)('a' + r.nextInt(26));
weijun@2416 622 // The last char cannot be a number, otherwise, keyForUser()
weijun@2416 623 // believes it's a sign of kvno
weijun@2416 624 pass[31] = 'Z';
weijun@681 625 return pass;
weijun@681 626 }
weijun@681 627
weijun@681 628 /**
weijun@681 629 * Generates a random key for the given encryption type.
weijun@681 630 * @param eType the encryption type
weijun@681 631 * @return the generated key
weijun@681 632 * @throws sun.security.krb5.KrbException for unknown/unsupported etype
weijun@681 633 */
weijun@681 634 private static EncryptionKey generateRandomKey(int eType)
weijun@681 635 throws KrbException {
weijun@681 636 // Is 32 enough for AES256? I should have generated the keys directly
weijun@681 637 // but different cryptos have different rules on what keys are valid.
weijun@681 638 char[] pass = randomPassword();
weijun@681 639 String algo;
weijun@681 640 switch (eType) {
weijun@681 641 case EncryptedData.ETYPE_DES_CBC_MD5: algo = "DES"; break;
weijun@681 642 case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: algo = "DESede"; break;
weijun@681 643 case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: algo = "AES128"; break;
weijun@681 644 case EncryptedData.ETYPE_ARCFOUR_HMAC: algo = "ArcFourHMAC"; break;
weijun@681 645 case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: algo = "AES256"; break;
weijun@681 646 default: algo = "DES"; break;
weijun@681 647 }
weijun@681 648 return new EncryptionKey(pass, "NOTHING", algo); // Silly
weijun@681 649 }
weijun@681 650
weijun@681 651 /**
weijun@681 652 * Returns the password for a given principal
weijun@681 653 * @param p principal
weijun@681 654 * @return the password
weijun@681 655 * @throws sun.security.krb5.KrbException when the principal is not inside
weijun@681 656 * the database.
weijun@681 657 */
weijun@1944 658 private char[] getPassword(PrincipalName p, boolean server)
weijun@1944 659 throws KrbException {
weijun@1303 660 String pn = p.toString();
weijun@1303 661 if (p.getRealmString() == null) {
weijun@1303 662 pn = pn + "@" + getRealm();
weijun@1303 663 }
weijun@1303 664 char[] pass = passwords.get(pn);
weijun@681 665 if (pass == null) {
weijun@1944 666 throw new KrbException(server?
weijun@1944 667 Krb5.KDC_ERR_S_PRINCIPAL_UNKNOWN:
weijun@6340 668 Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN, pn.toString());
weijun@681 669 }
weijun@681 670 return pass;
weijun@681 671 }
weijun@681 672
weijun@681 673 /**
weijun@1303 674 * Returns the salt string for the principal.
weijun@681 675 * @param p principal
weijun@681 676 * @return the salt
weijun@681 677 */
weijun@6340 678 protected String getSalt(PrincipalName p) {
weijun@3057 679 String pn = p.toString();
weijun@3057 680 if (p.getRealmString() == null) {
weijun@3057 681 pn = pn + "@" + getRealm();
weijun@3057 682 }
weijun@13813 683 if (salts.containsKey(pn)) {
weijun@13813 684 return salts.get(pn);
weijun@13813 685 }
weijun@3057 686 if (passwords.containsKey(pn)) {
weijun@3057 687 try {
weijun@3057 688 // Find the principal name with correct case.
weijun@3057 689 p = new PrincipalName(passwords.ceilingEntry(pn).getKey());
weijun@3057 690 } catch (RealmException re) {
weijun@3057 691 // Won't happen
weijun@3057 692 }
weijun@3057 693 }
weijun@1303 694 String s = p.getRealmString();
weijun@1303 695 if (s == null) s = getRealm();
weijun@1303 696 for (String n: p.getNameStrings()) {
weijun@1303 697 s += n;
weijun@681 698 }
weijun@1303 699 return s;
weijun@681 700 }
weijun@681 701
weijun@681 702 /**
weijun@13813 703 * Returns the s2kparams for the principal given the etype.
weijun@13813 704 * @param p principal
weijun@13813 705 * @param etype encryption type
weijun@13813 706 * @return the s2kparams, might be null
weijun@13813 707 */
weijun@13813 708 protected byte[] getParams(PrincipalName p, int etype) {
weijun@13813 709 switch (etype) {
weijun@13813 710 case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96:
weijun@13813 711 case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96:
weijun@13813 712 String pn = p.toString();
weijun@13813 713 if (p.getRealmString() == null) {
weijun@13813 714 pn = pn + "@" + getRealm();
weijun@13813 715 }
weijun@13813 716 if (s2kparamses.containsKey(pn)) {
weijun@13813 717 return s2kparamses.get(pn);
weijun@13813 718 }
weijun@13813 719 return new byte[] {0, 0, 0x10, 0};
weijun@13813 720 default:
weijun@13813 721 return null;
weijun@13813 722 }
weijun@13813 723 }
weijun@13813 724
weijun@13813 725 /**
weijun@681 726 * Returns the key for a given principal of the given encryption type
weijun@681 727 * @param p the principal
weijun@681 728 * @param etype the encryption type
weijun@1944 729 * @param server looking for a server principal?
weijun@681 730 * @return the key
weijun@681 731 * @throws sun.security.krb5.KrbException for unknown/unsupported etype
weijun@681 732 */
andrew@13824 733 EncryptionKey keyForUser(PrincipalName p, int etype, boolean server)
weijun@1944 734 throws KrbException {
weijun@681 735 try {
weijun@681 736 // Do not call EncryptionKey.acquireSecretKeys(), otherwise
weijun@681 737 // the krb5.conf config file would be loaded.
weijun@1805 738 Integer kvno = null;
weijun@2038 739 // For service whose password ending with a number, use it as kvno.
weijun@2038 740 // Kvno must be postive.
weijun@2038 741 if (p.toString().indexOf('/') > 0) {
weijun@1944 742 char[] pass = getPassword(p, server);
weijun@1805 743 if (Character.isDigit(pass[pass.length-1])) {
weijun@1805 744 kvno = pass[pass.length-1] - '0';
weijun@1805 745 }
weijun@1805 746 }
weijun@3057 747 return new EncryptionKey(EncryptionKeyDotStringToKey(
weijun@13813 748 getPassword(p, server), getSalt(p), getParams(p, etype), etype),
weijun@1805 749 etype, kvno);
weijun@1944 750 } catch (KrbException ke) {
weijun@1944 751 throw ke;
weijun@681 752 } catch (Exception e) {
weijun@681 753 throw new RuntimeException(e); // should not happen
weijun@681 754 }
weijun@681 755 }
weijun@681 756
weijun@681 757 /**
pkoppula@13316 758 * Returns a KerberosTime.
pkoppula@13316 759 *
pkoppula@13316 760 * @param offset offset from NOW in seconds
pkoppula@13316 761 */
pkoppula@13316 762 private static KerberosTime timeAfter(int offset) {
pkoppula@13316 763 return new KerberosTime(new Date().getTime() + offset * 1000L);
pkoppula@13316 764 }
pkoppula@13316 765
pkoppula@13316 766 /**
weijun@681 767 * Processes an incoming request and generates a response.
weijun@681 768 * @param in the request
weijun@681 769 * @return the response
weijun@681 770 * @throws java.lang.Exception for various errors
weijun@681 771 */
weijun@8933 772 protected byte[] processMessage(byte[] in) throws Exception {
weijun@681 773 if ((in[0] & 0x1f) == Krb5.KRB_AS_REQ)
weijun@681 774 return processAsReq(in);
weijun@681 775 else
weijun@681 776 return processTgsReq(in);
weijun@681 777 }
weijun@681 778
weijun@681 779 /**
weijun@681 780 * Processes a TGS_REQ and generates a TGS_REP (or KRB_ERROR)
weijun@681 781 * @param in the request
weijun@681 782 * @return the response
weijun@681 783 * @throws java.lang.Exception for various errors
weijun@681 784 */
weijun@8933 785 protected byte[] processTgsReq(byte[] in) throws Exception {
weijun@681 786 TGSReq tgsReq = new TGSReq(in);
weijun@4446 787 PrincipalName service = tgsReq.reqBody.sname;
weijun@4446 788 if (options.containsKey(KDC.Option.RESP_NT)) {
weijun@5623 789 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT),
weijun@5623 790 service.getNameStrings(), service.getRealm());
weijun@4446 791 }
weijun@681 792 try {
weijun@681 793 System.out.println(realm + "> " + tgsReq.reqBody.cname +
weijun@681 794 " sends TGS-REQ for " +
weijun@11700 795 service + ", " + tgsReq.reqBody.kdcOptions);
weijun@681 796 KDCReqBody body = tgsReq.reqBody;
andrew@13817 797 int[] eTypes = filterSupported(KDCReqBodyDotEType(body));
andrew@13817 798 if (eTypes.length == 0) {
andrew@13817 799 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
andrew@13817 800 }
weijun@3057 801 int e2 = eTypes[0]; // etype for outgoing session key
weijun@3057 802 int e3 = eTypes[0]; // etype for outgoing ticket
weijun@681 803
andrew@13824 804 PAData[] pas = tgsReq.pAData;
weijun@681 805
weijun@681 806 Ticket tkt = null;
weijun@681 807 EncTicketPart etp = null;
weijun@6093 808
weijun@6093 809 PrincipalName cname = null;
weijun@6093 810 boolean allowForwardable = true;
andrew@13820 811 boolean isReferral = false;
mbalao@13811 812 if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) {
andrew@13820 813 System.out.println(realm + "> verifying referral for " +
andrew@13820 814 body.sname.getNameString());
mbalao@13811 815 KDC referral = aliasReferrals.get(body.sname.getNameString());
mbalao@13811 816 if (referral != null) {
mbalao@13811 817 service = new PrincipalName(
mbalao@13811 818 PrincipalName.TGS_DEFAULT_SRV_NAME +
mbalao@13811 819 PrincipalName.NAME_COMPONENT_SEPARATOR_STR +
mbalao@13811 820 referral.getRealm(), PrincipalName.KRB_NT_SRV_INST,
mbalao@13811 821 this.getRealm());
andrew@13820 822 System.out.println(realm + "> referral to " +
andrew@13820 823 referral.getRealm());
andrew@13820 824 isReferral = true;
mbalao@13811 825 }
mbalao@13811 826 }
mbalao@13811 827
weijun@681 828 if (pas == null || pas.length == 0) {
weijun@681 829 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);
weijun@681 830 } else {
weijun@6093 831 PrincipalName forUserCName = null;
weijun@681 832 for (PAData pa: pas) {
weijun@681 833 if (pa.getType() == Krb5.PA_TGS_REQ) {
weijun@681 834 APReq apReq = new APReq(pa.getValue());
weijun@681 835 tkt = apReq.ticket;
weijun@3057 836 int te = tkt.encPart.getEType();
weijun@3057 837 EncryptionKey kkey = keyForUser(tkt.sname, te, true);
weijun@681 838 byte[] bb = tkt.encPart.decrypt(kkey, KeyUsage.KU_TICKET);
weijun@681 839 DerInputStream derIn = new DerInputStream(bb);
weijun@681 840 DerValue der = derIn.getDerValue();
weijun@681 841 etp = new EncTicketPart(der.toByteArray());
weijun@6093 842 // Finally, cname will be overwritten by PA-FOR-USER
weijun@6093 843 // if it exists.
weijun@6093 844 cname = etp.cname;
weijun@6093 845 System.out.println(realm + "> presenting a ticket of "
weijun@6093 846 + etp.cname + " to " + tkt.sname);
weijun@6093 847 } else if (pa.getType() == Krb5.PA_FOR_USER) {
weijun@6093 848 if (options.containsKey(Option.ALLOW_S4U2SELF)) {
weijun@6093 849 PAForUserEnc p4u = new PAForUserEnc(
weijun@6093 850 new DerValue(pa.getValue()), null);
weijun@6093 851 forUserCName = p4u.name;
andrew@13817 852 System.out.println(realm + "> See PA_FOR_USER "
weijun@6093 853 + " in the name of " + p4u.name);
weijun@6093 854 }
weijun@681 855 }
weijun@681 856 }
weijun@6093 857 if (forUserCName != null) {
andrew@13817 858 List<String> names = (List<String>)
andrew@13817 859 options.get(Option.ALLOW_S4U2SELF);
weijun@6093 860 if (!names.contains(cname.toString())) {
weijun@6093 861 // Mimic the normal KDC behavior. When a server is not
weijun@6093 862 // allowed to send S4U2self, do not send an error.
weijun@6093 863 // Instead, send a ticket which is useless later.
weijun@6093 864 allowForwardable = false;
weijun@6093 865 }
weijun@6093 866 cname = forUserCName;
weijun@6093 867 }
weijun@681 868 if (tkt == null) {
weijun@681 869 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);
weijun@681 870 }
weijun@681 871 }
weijun@681 872
weijun@681 873 // Session key for original ticket, TGT
weijun@681 874 EncryptionKey ckey = etp.key;
weijun@681 875
weijun@681 876 // Session key for session with the service
weijun@3057 877 EncryptionKey key = generateRandomKey(e2);
weijun@681 878
weijun@681 879 // Check time, TODO
andrew@13817 880 KerberosTime from = body.from;
weijun@681 881 KerberosTime till = body.till;
andrew@13817 882 if (from == null || from.isZero()) {
andrew@13817 883 from = timeAfter(0);
andrew@13817 884 }
andrew@13814 885 KerberosTime rtime = body.rtime;
weijun@681 886 if (till == null) {
weijun@681 887 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
weijun@681 888 } else if (till.isZero()) {
andrew@13817 889 till = timeAfter(DEFAULT_LIFETIME);
andrew@13814 890 }
andrew@13814 891 if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {
andrew@13817 892 rtime = timeAfter(DEFAULT_RENEWTIME);
weijun@681 893 }
weijun@681 894
weijun@681 895 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
weijun@6093 896 if (body.kdcOptions.get(KDCOptions.FORWARDABLE)
weijun@6093 897 && allowForwardable) {
weijun@11700 898 List<String> sensitives = (List<String>)
weijun@11700 899 options.get(Option.SENSITIVE_ACCOUNTS);
weijun@11700 900 if (sensitives != null && sensitives.contains(cname.toString())) {
weijun@11700 901 // Cannot make FORWARDABLE
weijun@11700 902 } else {
weijun@11700 903 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true;
weijun@11700 904 }
weijun@681 905 }
andrew@13844 906 // We do not request for addresses for FORWARDED tickets
andrew@13843 907 if (options.containsKey(Option.CHECK_ADDRESSES)
andrew@13843 908 && body.kdcOptions.get(KDCOptions.FORWARDED)
andrew@13844 909 && body.addresses != null) {
andrew@13843 910 throw new KrbException(Krb5.KDC_ERR_BADOPTION);
andrew@13843 911 }
weijun@681 912 if (body.kdcOptions.get(KDCOptions.FORWARDED) ||
weijun@681 913 etp.flags.get(Krb5.TKT_OPTS_FORWARDED)) {
weijun@681 914 bFlags[Krb5.TKT_OPTS_FORWARDED] = true;
weijun@681 915 }
weijun@681 916 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) {
weijun@681 917 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true;
andrew@13817 918 //renew = timeAfter(3600 * 24 * 7);
weijun@681 919 }
weijun@681 920 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {
weijun@681 921 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;
weijun@681 922 }
weijun@681 923 if (body.kdcOptions.get(KDCOptions.POSTDATED)) {
weijun@681 924 bFlags[Krb5.TKT_OPTS_POSTDATED] = true;
weijun@681 925 }
weijun@681 926 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) {
weijun@681 927 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true;
weijun@681 928 }
mbalao@13897 929 if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT)) {
weijun@6093 930 if (!options.containsKey(Option.ALLOW_S4U2PROXY)) {
weijun@6093 931 // Don't understand CNAME_IN_ADDL_TKT
weijun@6093 932 throw new KrbException(Krb5.KDC_ERR_BADOPTION);
weijun@6093 933 } else {
weijun@6093 934 Map<String,List<String>> map = (Map<String,List<String>>)
weijun@6093 935 options.get(Option.ALLOW_S4U2PROXY);
weijun@6093 936 Ticket second = KDCReqBodyDotFirstAdditionalTicket(body);
andrew@13817 937 EncryptionKey key2 = keyForUser(
andrew@13817 938 second.sname, second.encPart.getEType(), true);
weijun@6093 939 byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET);
weijun@6093 940 DerInputStream derIn = new DerInputStream(bb);
weijun@6093 941 DerValue der = derIn.getDerValue();
weijun@6093 942 EncTicketPart tktEncPart = new EncTicketPart(der.toByteArray());
weijun@6093 943 if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)) {
weijun@6093 944 //throw new KrbException(Krb5.KDC_ERR_BADOPTION);
weijun@6093 945 }
weijun@6093 946 PrincipalName client = tktEncPart.cname;
weijun@6093 947 System.out.println(realm + "> and an additional ticket of "
weijun@6093 948 + client + " to " + second.sname);
weijun@6093 949 if (map.containsKey(cname.toString())) {
weijun@6093 950 if (map.get(cname.toString()).contains(service.toString())) {
weijun@6093 951 System.out.println(realm + "> S4U2proxy OK");
weijun@6093 952 } else {
weijun@6093 953 throw new KrbException(Krb5.KDC_ERR_BADOPTION);
weijun@6093 954 }
weijun@6093 955 } else {
weijun@6093 956 throw new KrbException(Krb5.KDC_ERR_BADOPTION);
weijun@6093 957 }
weijun@6093 958 cname = client;
weijun@6093 959 }
weijun@6093 960 }
weijun@1944 961
weijun@5485 962 String okAsDelegate = (String)options.get(Option.OK_AS_DELEGATE);
weijun@5485 963 if (okAsDelegate != null && (
weijun@5485 964 okAsDelegate.isEmpty() ||
weijun@5485 965 okAsDelegate.contains(service.getNameString()))) {
weijun@1944 966 bFlags[Krb5.TKT_OPTS_DELEGATE] = true;
weijun@1944 967 }
weijun@681 968 bFlags[Krb5.TKT_OPTS_INITIAL] = true;
weijun@681 969
andrew@13816 970 KerberosTime renewTill = etp.renewTill;
andrew@13816 971 if (renewTill != null && body.kdcOptions.get(KDCOptions.RENEW)) {
andrew@13816 972 // till should never pass renewTill
andrew@13816 973 if (till.greaterThan(renewTill)) {
andrew@13816 974 till = renewTill;
andrew@13816 975 }
andrew@13816 976 if (System.getProperty("test.set.null.renew") != null) {
andrew@13816 977 // Testing 8186576, see NullRenewUntil.java.
andrew@13816 978 renewTill = null;
andrew@13816 979 }
andrew@13816 980 }
andrew@13816 981
weijun@681 982 TicketFlags tFlags = new TicketFlags(bFlags);
weijun@681 983 EncTicketPart enc = new EncTicketPart(
weijun@681 984 tFlags,
weijun@681 985 key,
weijun@6093 986 cname,
weijun@681 987 new TransitedEncoding(1, new byte[0]), // TODO
andrew@13817 988 timeAfter(0),
andrew@13817 989 from,
andrew@13816 990 till, renewTill,
andrew@13843 991 body.addresses != null ? body.addresses
andrew@13843 992 : etp.caddr,
weijun@681 993 null);
weijun@4446 994 EncryptionKey skey = keyForUser(service, e3, true);
weijun@3057 995 if (skey == null) {
weijun@3057 996 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO
weijun@3057 997 }
weijun@681 998 Ticket t = new Ticket(
rpatil@12443 999 System.getProperty("test.kdc.diff.sname") != null ?
rpatil@12443 1000 new PrincipalName("xx" + service.toString()) :
rpatil@12443 1001 service,
weijun@681 1002 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET)
weijun@681 1003 );
weijun@681 1004 EncTGSRepPart enc_part = new EncTGSRepPart(
weijun@681 1005 key,
andrew@13817 1006 new LastReq(new LastReqEntry[] {
andrew@13817 1007 new LastReqEntry(0, timeAfter(-10))
weijun@681 1008 }),
weijun@681 1009 body.getNonce(), // TODO: detect replay
andrew@13817 1010 timeAfter(3600 * 24),
weijun@681 1011 // Next 5 and last MUST be same with ticket
weijun@681 1012 tFlags,
andrew@13817 1013 timeAfter(0),
andrew@13817 1014 from,
andrew@13816 1015 till, renewTill,
weijun@4446 1016 service,
andrew@13843 1017 body.addresses,
mbalao@13811 1018 null
weijun@681 1019 );
andrew@13817 1020 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(),
andrew@13817 1021 KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY);
weijun@681 1022 TGSRep tgsRep = new TGSRep(null,
weijun@6093 1023 cname,
weijun@681 1024 t,
weijun@681 1025 edata);
weijun@681 1026 System.out.println(" Return " + tgsRep.cname
weijun@11700 1027 + " ticket for " + tgsRep.ticket.sname + ", flags "
weijun@11700 1028 + tFlags);
weijun@681 1029
weijun@681 1030 DerOutputStream out = new DerOutputStream();
weijun@681 1031 out.write(DerValue.createTag(DerValue.TAG_APPLICATION,
weijun@681 1032 true, (byte)Krb5.KRB_TGS_REP), tgsRep.asn1Encode());
weijun@681 1033 return out.toByteArray();
weijun@681 1034 } catch (KrbException ke) {
weijun@681 1035 ke.printStackTrace(System.out);
weijun@681 1036 KRBError kerr = ke.getError();
weijun@681 1037 KDCReqBody body = tgsReq.reqBody;
weijun@681 1038 System.out.println(" Error " + ke.returnCode()
weijun@681 1039 + " " +ke.returnCodeMessage());
weijun@681 1040 if (kerr == null) {
weijun@681 1041 kerr = new KRBError(null, null, null,
andrew@13817 1042 timeAfter(0),
weijun@681 1043 0,
weijun@681 1044 ke.returnCode(),
weijun@5623 1045 body.cname,
weijun@5623 1046 service,
weijun@681 1047 KrbException.errorMessage(ke.returnCode()),
weijun@681 1048 null);
weijun@681 1049 }
weijun@681 1050 return kerr.asn1Encode();
weijun@681 1051 }
weijun@681 1052 }
weijun@681 1053
weijun@681 1054 /**
weijun@681 1055 * Processes a AS_REQ and generates a AS_REP (or KRB_ERROR)
weijun@681 1056 * @param in the request
weijun@681 1057 * @return the response
weijun@681 1058 * @throws java.lang.Exception for various errors
weijun@681 1059 */
weijun@8933 1060 protected byte[] processAsReq(byte[] in) throws Exception {
weijun@681 1061 ASReq asReq = new ASReq(in);
mbalao@13811 1062 byte[] asReqbytes = asReq.asn1Encode();
weijun@681 1063 int[] eTypes = null;
smarks@3391 1064 List<PAData> outPAs = new ArrayList<>();
weijun@3057 1065
weijun@4446 1066 PrincipalName service = asReq.reqBody.sname;
weijun@4446 1067 if (options.containsKey(KDC.Option.RESP_NT)) {
weijun@11122 1068 service = new PrincipalName((int)options.get(KDC.Option.RESP_NT),
weijun@11122 1069 service.getNameStrings(),
weijun@11122 1070 Realm.getDefault());
weijun@4446 1071 }
weijun@681 1072 try {
weijun@681 1073 System.out.println(realm + "> " + asReq.reqBody.cname +
weijun@681 1074 " sends AS-REQ for " +
weijun@11700 1075 service + ", " + asReq.reqBody.kdcOptions);
weijun@681 1076
weijun@681 1077 KDCReqBody body = asReq.reqBody;
weijun@681 1078
andrew@13817 1079 eTypes = filterSupported(KDCReqBodyDotEType(body));
andrew@13817 1080 if (eTypes.length == 0) {
andrew@13817 1081 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
andrew@13817 1082 }
weijun@681 1083 int eType = eTypes[0];
weijun@681 1084
andrew@13820 1085 if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) {
mbalao@13811 1086 PrincipalName principal = alias2Principals.get(
mbalao@13811 1087 body.cname.getNameString());
mbalao@13811 1088 if (principal != null) {
mbalao@13811 1089 body.cname = principal;
mbalao@13811 1090 } else {
mbalao@13811 1091 KDC referral = aliasReferrals.get(body.cname.getNameString());
mbalao@13811 1092 if (referral != null) {
mbalao@13811 1093 body.cname = new PrincipalName(
mbalao@13811 1094 PrincipalName.TGS_DEFAULT_SRV_NAME,
mbalao@13811 1095 PrincipalName.KRB_NT_SRV_INST,
mbalao@13811 1096 referral.getRealm());
mbalao@13811 1097 throw new KrbException(Krb5.KRB_ERR_WRONG_REALM);
mbalao@13811 1098 }
mbalao@13811 1099 }
mbalao@13811 1100 }
mbalao@13811 1101
weijun@1944 1102 EncryptionKey ckey = keyForUser(body.cname, eType, false);
weijun@4446 1103 EncryptionKey skey = keyForUser(service, eType, true);
weijun@2464 1104
weijun@2464 1105 if (options.containsKey(KDC.Option.ONLY_RC4_TGT)) {
weijun@2464 1106 int tgtEType = EncryptedData.ETYPE_ARCFOUR_HMAC;
weijun@2464 1107 boolean found = false;
weijun@2464 1108 for (int i=0; i<eTypes.length; i++) {
weijun@2464 1109 if (eTypes[i] == tgtEType) {
weijun@2464 1110 found = true;
weijun@2464 1111 break;
weijun@2464 1112 }
weijun@2464 1113 }
weijun@2464 1114 if (!found) {
weijun@2464 1115 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
weijun@2464 1116 }
weijun@4446 1117 skey = keyForUser(service, tgtEType, true);
weijun@2464 1118 }
weijun@681 1119 if (ckey == null) {
weijun@681 1120 throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
weijun@681 1121 }
weijun@681 1122 if (skey == null) {
weijun@681 1123 throw new KrbException(Krb5.KDC_ERR_SUMTYPE_NOSUPP); // TODO
weijun@681 1124 }
weijun@681 1125
weijun@681 1126 // Session key
weijun@681 1127 EncryptionKey key = generateRandomKey(eType);
weijun@681 1128 // Check time, TODO
andrew@13817 1129 KerberosTime from = body.from;
weijun@681 1130 KerberosTime till = body.till;
pkoppula@13316 1131 KerberosTime rtime = body.rtime;
andrew@13817 1132 if (from == null || from.isZero()) {
andrew@13817 1133 from = timeAfter(0);
andrew@13817 1134 }
weijun@681 1135 if (till == null) {
weijun@681 1136 throw new KrbException(Krb5.KDC_ERR_NEVER_VALID); // TODO
weijun@681 1137 } else if (till.isZero()) {
pkoppula@13316 1138 String ttlsVal = System.getProperty("test.kdc.ttl.value");
pkoppula@13316 1139 if (ttlsVal != null){
pkoppula@13316 1140 till = timeAfter(duration(ttlsVal));
pkoppula@13316 1141 if (till.greaterThan(timeAfter(24 * 3600)) &&
pkoppula@13316 1142 (System.getProperty("test.kdc.force.till") == null)) {
pkoppula@13316 1143 till = timeAfter(DEFAULT_LIFETIME);
pkoppula@13316 1144 body.kdcOptions.set(KDCOptions.RENEWABLE, true);
pkoppula@13316 1145 }
pkoppula@13316 1146 } else {
pkoppula@13316 1147 till = timeAfter(DEFAULT_LIFETIME);
pkoppula@13316 1148 }
weijun@681 1149 }
pkoppula@13316 1150
pkoppula@13316 1151 if (rtime == null && body.kdcOptions.get(KDCOptions.RENEWABLE)) {
pkoppula@13316 1152 rtime = timeAfter(DEFAULT_RENEWTIME);
pkoppula@13316 1153 }
pkoppula@13316 1154
weijun@681 1155 //body.from
weijun@681 1156 boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
weijun@681 1157 if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) {
weijun@11700 1158 List<String> sensitives = (List<String>)
weijun@11700 1159 options.get(Option.SENSITIVE_ACCOUNTS);
andrew@13817 1160 if (sensitives != null
andrew@13817 1161 && sensitives.contains(body.cname.toString())) {
weijun@11700 1162 // Cannot make FORWARDABLE
weijun@11700 1163 } else {
weijun@11700 1164 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true;
weijun@11700 1165 }
weijun@681 1166 }
weijun@681 1167 if (body.kdcOptions.get(KDCOptions.RENEWABLE)) {
weijun@681 1168 bFlags[Krb5.TKT_OPTS_RENEWABLE] = true;
andrew@13817 1169 //renew = timeAfter(3600 * 24 * 7);
weijun@681 1170 }
weijun@681 1171 if (body.kdcOptions.get(KDCOptions.PROXIABLE)) {
weijun@681 1172 bFlags[Krb5.TKT_OPTS_PROXIABLE] = true;
weijun@681 1173 }
weijun@681 1174 if (body.kdcOptions.get(KDCOptions.POSTDATED)) {
weijun@681 1175 bFlags[Krb5.TKT_OPTS_POSTDATED] = true;
weijun@681 1176 }
weijun@681 1177 if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) {
weijun@681 1178 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true;
weijun@681 1179 }
weijun@681 1180 bFlags[Krb5.TKT_OPTS_INITIAL] = true;
mbalao@14203 1181 if (System.getProperty("test.kdc.always.enc.pa.rep") != null) {
mbalao@14203 1182 bFlags[Krb5.TKT_OPTS_ENC_PA_REP] = true;
mbalao@14203 1183 }
weijun@681 1184
weijun@3057 1185 // Creating PA-DATA
weijun@4558 1186 DerValue[] pas2 = null, pas = null;
weijun@4558 1187 if (options.containsKey(KDC.Option.DUP_ETYPE)) {
weijun@4558 1188 int n = (Integer)options.get(KDC.Option.DUP_ETYPE);
weijun@4558 1189 switch (n) {
weijun@4558 1190 case 1: // customer's case in 7067974
weijun@4558 1191 pas2 = new DerValue[] {
weijun@4558 1192 new DerValue(new ETypeInfo2(1, null, null).asn1Encode()),
weijun@4558 1193 new DerValue(new ETypeInfo2(1, "", null).asn1Encode()),
andrew@13817 1194 new DerValue(new ETypeInfo2(
andrew@13817 1195 1, realm, new byte[]{1}).asn1Encode()),
weijun@4558 1196 };
weijun@4558 1197 pas = new DerValue[] {
weijun@4558 1198 new DerValue(new ETypeInfo(1, null).asn1Encode()),
weijun@4558 1199 new DerValue(new ETypeInfo(1, "").asn1Encode()),
weijun@6816 1200 new DerValue(new ETypeInfo(1, realm).asn1Encode()),
weijun@4558 1201 };
weijun@4558 1202 break;
weijun@4558 1203 case 2: // we still reject non-null s2kparams and prefer E2 over E
weijun@4558 1204 pas2 = new DerValue[] {
andrew@13817 1205 new DerValue(new ETypeInfo2(
andrew@13817 1206 1, realm, new byte[]{1}).asn1Encode()),
weijun@4558 1207 new DerValue(new ETypeInfo2(1, null, null).asn1Encode()),
weijun@4558 1208 new DerValue(new ETypeInfo2(1, "", null).asn1Encode()),
weijun@4558 1209 };
weijun@4558 1210 pas = new DerValue[] {
weijun@6816 1211 new DerValue(new ETypeInfo(1, realm).asn1Encode()),
weijun@4558 1212 new DerValue(new ETypeInfo(1, null).asn1Encode()),
weijun@4558 1213 new DerValue(new ETypeInfo(1, "").asn1Encode()),
weijun@4558 1214 };
weijun@4558 1215 break;
weijun@4558 1216 case 3: // but only E is wrong
weijun@4558 1217 pas = new DerValue[] {
weijun@6816 1218 new DerValue(new ETypeInfo(1, realm).asn1Encode()),
weijun@4558 1219 new DerValue(new ETypeInfo(1, null).asn1Encode()),
weijun@4558 1220 new DerValue(new ETypeInfo(1, "").asn1Encode()),
weijun@4558 1221 };
weijun@4558 1222 break;
weijun@4558 1223 case 4: // we also ignore rc4-hmac
weijun@4558 1224 pas = new DerValue[] {
weijun@4558 1225 new DerValue(new ETypeInfo(23, "ANYTHING").asn1Encode()),
weijun@4558 1226 new DerValue(new ETypeInfo(1, null).asn1Encode()),
weijun@4558 1227 new DerValue(new ETypeInfo(1, "").asn1Encode()),
weijun@4558 1228 };
weijun@4558 1229 break;
weijun@4558 1230 case 5: // "" should be wrong, but we accept it now
weijun@4558 1231 // See s.s.k.internal.PAData$SaltAndParams
weijun@4558 1232 pas = new DerValue[] {
weijun@4558 1233 new DerValue(new ETypeInfo(1, "").asn1Encode()),
weijun@4558 1234 new DerValue(new ETypeInfo(1, null).asn1Encode()),
weijun@4558 1235 };
weijun@4558 1236 break;
weijun@4558 1237 }
weijun@4558 1238 } else {
weijun@4558 1239 int[] epas = eTypes;
weijun@4558 1240 if (options.containsKey(KDC.Option.RC4_FIRST_PREAUTH)) {
weijun@4558 1241 for (int i=1; i<epas.length; i++) {
weijun@4558 1242 if (epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC) {
weijun@4558 1243 epas[i] = epas[0];
weijun@4558 1244 epas[0] = EncryptedData.ETYPE_ARCFOUR_HMAC;
weijun@4558 1245 break;
weijun@4558 1246 }
weijun@4558 1247 };
weijun@4558 1248 } else if (options.containsKey(KDC.Option.ONLY_ONE_PREAUTH)) {
weijun@4558 1249 epas = new int[] { eTypes[0] };
weijun@4558 1250 }
weijun@4558 1251 pas2 = new DerValue[epas.length];
weijun@4558 1252 for (int i=0; i<epas.length; i++) {
weijun@4558 1253 pas2[i] = new DerValue(new ETypeInfo2(
weijun@4558 1254 epas[i],
weijun@4558 1255 epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ?
weijun@4558 1256 null : getSalt(body.cname),
weijun@13813 1257 getParams(body.cname, epas[i])).asn1Encode());
weijun@4558 1258 }
weijun@4558 1259 boolean allOld = true;
weijun@4558 1260 for (int i: eTypes) {
weijun@4558 1261 if (i == EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96 ||
weijun@4558 1262 i == EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96) {
weijun@4558 1263 allOld = false;
weijun@3057 1264 break;
weijun@3057 1265 }
weijun@4558 1266 }
weijun@4558 1267 if (allOld) {
weijun@4558 1268 pas = new DerValue[epas.length];
weijun@4558 1269 for (int i=0; i<epas.length; i++) {
weijun@4558 1270 pas[i] = new DerValue(new ETypeInfo(
weijun@4558 1271 epas[i],
weijun@4558 1272 epas[i] == EncryptedData.ETYPE_ARCFOUR_HMAC ?
weijun@4558 1273 null : getSalt(body.cname)
weijun@4558 1274 ).asn1Encode());
weijun@4558 1275 }
weijun@4558 1276 }
weijun@3057 1277 }
weijun@3057 1278
weijun@4558 1279 DerOutputStream eid;
weijun@4558 1280 if (pas2 != null) {
weijun@4558 1281 eid = new DerOutputStream();
weijun@4558 1282 eid.putSequence(pas2);
weijun@4558 1283 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO2, eid.toByteArray()));
weijun@3057 1284 }
weijun@4558 1285 if (pas != null) {
weijun@3057 1286 eid = new DerOutputStream();
weijun@3057 1287 eid.putSequence(pas);
weijun@3057 1288 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray()));
weijun@3057 1289 }
weijun@3057 1290
andrew@13824 1291 PAData[] inPAs = asReq.pAData;
mbalao@13811 1292 List<PAData> enc_outPAs = new ArrayList<>();
weijun@13818 1293
weijun@13818 1294 byte[] paEncTimestamp = null;
weijun@13818 1295 if (inPAs != null) {
weijun@13818 1296 for (PAData inPA : inPAs) {
weijun@13818 1297 if (inPA.getType() == Krb5.PA_ENC_TIMESTAMP) {
weijun@13818 1298 paEncTimestamp = inPA.getValue();
weijun@13818 1299 }
weijun@13818 1300 }
weijun@13818 1301 }
weijun@13818 1302
weijun@13818 1303 if (paEncTimestamp == null) {
weijun@681 1304 Object preauth = options.get(Option.PREAUTH_REQUIRED);
weijun@681 1305 if (preauth == null || preauth.equals(Boolean.TRUE)) {
weijun@681 1306 throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED);
weijun@681 1307 }
weijun@681 1308 } else {
mbalao@13811 1309 EncryptionKey pakey = null;
weijun@681 1310 try {
andrew@13817 1311 EncryptedData data = newEncryptedData(
weijun@13818 1312 new DerValue(paEncTimestamp));
mbalao@13811 1313 pakey = keyForUser(body.cname, data.getEType(), false);
weijun@2464 1314 data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS);
weijun@681 1315 } catch (Exception e) {
andrew@13817 1316 KrbException ke = new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED);
andrew@13817 1317 ke.initCause(e);
andrew@13817 1318 throw ke;
weijun@681 1319 }
weijun@681 1320 bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true;
mbalao@13811 1321 for (PAData pa : inPAs) {
mbalao@13811 1322 if (pa.getType() == Krb5.PA_REQ_ENC_PA_REP) {
mbalao@13811 1323 Checksum ckSum = new Checksum(
mbalao@13811 1324 Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128,
mbalao@13811 1325 asReqbytes, ckey, KeyUsage.KU_AS_REQ);
mbalao@13811 1326 enc_outPAs.add(new PAData(Krb5.PA_REQ_ENC_PA_REP,
mbalao@13811 1327 ckSum.asn1Encode()));
mbalao@13811 1328 bFlags[Krb5.TKT_OPTS_ENC_PA_REP] = true;
mbalao@13811 1329 break;
mbalao@13811 1330 }
mbalao@13811 1331 }
weijun@681 1332 }
weijun@681 1333
weijun@681 1334 TicketFlags tFlags = new TicketFlags(bFlags);
weijun@681 1335 EncTicketPart enc = new EncTicketPart(
weijun@681 1336 tFlags,
weijun@681 1337 key,
weijun@681 1338 body.cname,
weijun@681 1339 new TransitedEncoding(1, new byte[0]),
andrew@13817 1340 timeAfter(0),
andrew@13817 1341 from,
pkoppula@13316 1342 till, rtime,
weijun@681 1343 body.addresses,
weijun@681 1344 null);
weijun@681 1345 Ticket t = new Ticket(
weijun@4446 1346 service,
weijun@681 1347 new EncryptedData(skey, enc.asn1Encode(), KeyUsage.KU_TICKET)
weijun@681 1348 );
weijun@681 1349 EncASRepPart enc_part = new EncASRepPart(
weijun@681 1350 key,
weijun@681 1351 new LastReq(new LastReqEntry[]{
andrew@13817 1352 new LastReqEntry(0, timeAfter(-10))
weijun@681 1353 }),
weijun@681 1354 body.getNonce(), // TODO: detect replay?
andrew@13817 1355 timeAfter(3600 * 24),
weijun@681 1356 // Next 5 and last MUST be same with ticket
weijun@681 1357 tFlags,
andrew@13817 1358 timeAfter(0),
andrew@13817 1359 from,
pkoppula@13316 1360 till, rtime,
weijun@4446 1361 service,
mbalao@13811 1362 body.addresses,
mbalao@13811 1363 enc_outPAs.toArray(new PAData[enc_outPAs.size()])
weijun@681 1364 );
andrew@13817 1365 EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(),
andrew@13817 1366 KeyUsage.KU_ENC_AS_REP_PART);
weijun@3057 1367 ASRep asRep = new ASRep(
weijun@3057 1368 outPAs.toArray(new PAData[outPAs.size()]),
weijun@681 1369 body.cname,
weijun@681 1370 t,
weijun@681 1371 edata);
weijun@681 1372
weijun@681 1373 System.out.println(" Return " + asRep.cname
weijun@11700 1374 + " ticket for " + asRep.ticket.sname + ", flags "
weijun@11700 1375 + tFlags);
weijun@681 1376
weijun@681 1377 DerOutputStream out = new DerOutputStream();
weijun@681 1378 out.write(DerValue.createTag(DerValue.TAG_APPLICATION,
weijun@681 1379 true, (byte)Krb5.KRB_AS_REP), asRep.asn1Encode());
weijun@707 1380 byte[] result = out.toByteArray();
weijun@707 1381
weijun@707 1382 // Added feature:
weijun@707 1383 // Write the current issuing TGT into a ccache file specified
weijun@707 1384 // by the system property below.
weijun@707 1385 String ccache = System.getProperty("test.kdc.save.ccache");
weijun@707 1386 if (ccache != null) {
weijun@707 1387 asRep.encKDCRepPart = enc_part;
weijun@707 1388 sun.security.krb5.internal.ccache.Credentials credentials =
weijun@707 1389 new sun.security.krb5.internal.ccache.Credentials(asRep);
weijun@707 1390 CredentialsCache cache =
weijun@707 1391 CredentialsCache.create(asReq.reqBody.cname, ccache);
weijun@707 1392 if (cache == null) {
weijun@707 1393 throw new IOException("Unable to create the cache file " +
weijun@707 1394 ccache);
weijun@707 1395 }
weijun@707 1396 cache.update(credentials);
weijun@707 1397 cache.save();
weijun@707 1398 }
weijun@707 1399
weijun@707 1400 return result;
weijun@681 1401 } catch (KrbException ke) {
weijun@681 1402 ke.printStackTrace(System.out);
weijun@681 1403 KRBError kerr = ke.getError();
weijun@681 1404 KDCReqBody body = asReq.reqBody;
weijun@681 1405 System.out.println(" Error " + ke.returnCode()
weijun@681 1406 + " " +ke.returnCodeMessage());
weijun@681 1407 byte[] eData = null;
weijun@681 1408 if (kerr == null) {
weijun@681 1409 if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED ||
weijun@681 1410 ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) {
mbalao@13811 1411 outPAs.add(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]));
mbalao@13811 1412 }
mbalao@13811 1413 if (outPAs.size() > 0) {
weijun@681 1414 DerOutputStream bytes = new DerOutputStream();
weijun@3057 1415 for (PAData p: outPAs) {
weijun@3057 1416 bytes.write(p.asn1Encode());
weijun@681 1417 }
weijun@681 1418 DerOutputStream temp = new DerOutputStream();
weijun@681 1419 temp.write(DerValue.tag_Sequence, bytes);
weijun@681 1420 eData = temp.toByteArray();
weijun@681 1421 }
weijun@681 1422 kerr = new KRBError(null, null, null,
andrew@13817 1423 timeAfter(0),
weijun@681 1424 0,
weijun@681 1425 ke.returnCode(),
weijun@5623 1426 body.cname,
weijun@5623 1427 service,
weijun@681 1428 KrbException.errorMessage(ke.returnCode()),
weijun@681 1429 eData);
weijun@681 1430 }
weijun@681 1431 return kerr.asn1Encode();
weijun@681 1432 }
weijun@681 1433 }
weijun@681 1434
weijun@681 1435 /**
pkoppula@13316 1436 * Translates a duration value into seconds.
pkoppula@13316 1437 *
pkoppula@13316 1438 * The format can be one of "h:m[:s]", "NdNhNmNs", and "N". See
pkoppula@13316 1439 * http://web.mit.edu/kerberos/krb5-devel/doc/basic/date_format.html#duration
pkoppula@13316 1440 * for definitions.
pkoppula@13316 1441 *
pkoppula@13316 1442 * @param s the string duration
pkoppula@13316 1443 * @return time in seconds
pkoppula@13316 1444 * @throw KrbException if format is illegal
pkoppula@13316 1445 */
pkoppula@13316 1446 public static int duration(String s) throws KrbException {
pkoppula@13316 1447
pkoppula@13316 1448 if (s.isEmpty()) {
pkoppula@13316 1449 throw new KrbException("Duration cannot be empty");
pkoppula@13316 1450 }
pkoppula@13316 1451
pkoppula@13316 1452 // N
pkoppula@13316 1453 if (s.matches("\\d+")) {
pkoppula@13316 1454 return Integer.parseInt(s);
pkoppula@13316 1455 }
pkoppula@13316 1456
pkoppula@13316 1457 // h:m[:s]
pkoppula@13316 1458 Matcher m = Pattern.compile("(\\d+):(\\d+)(:(\\d+))?").matcher(s);
pkoppula@13316 1459 if (m.matches()) {
pkoppula@13316 1460 int hr = Integer.parseInt(m.group(1));
pkoppula@13316 1461 int min = Integer.parseInt(m.group(2));
pkoppula@13316 1462 if (min >= 60) {
pkoppula@13316 1463 throw new KrbException("Illegal duration format " + s);
pkoppula@13316 1464 }
pkoppula@13316 1465 int result = hr * 3600 + min * 60;
pkoppula@13316 1466 if (m.group(4) != null) {
pkoppula@13316 1467 int sec = Integer.parseInt(m.group(4));
pkoppula@13316 1468 if (sec >= 60) {
pkoppula@13316 1469 throw new KrbException("Illegal duration format " + s);
pkoppula@13316 1470 }
pkoppula@13316 1471 result += sec;
pkoppula@13316 1472 }
pkoppula@13316 1473 return result;
pkoppula@13316 1474 }
pkoppula@13316 1475
pkoppula@13316 1476 // NdNhNmNs
pkoppula@13316 1477 // 120m allowed. Maybe 1h120m is not good, but still allowed
pkoppula@13316 1478 m = Pattern.compile(
pkoppula@13316 1479 "((\\d+)d)?\\s*((\\d+)h)?\\s*((\\d+)m)?\\s*((\\d+)s)?",
pkoppula@13316 1480 Pattern.CASE_INSENSITIVE).matcher(s);
pkoppula@13316 1481 if (m.matches()) {
pkoppula@13316 1482 int result = 0;
pkoppula@13316 1483 if (m.group(2) != null) {
pkoppula@13316 1484 result += 86400 * Integer.parseInt(m.group(2));
pkoppula@13316 1485 }
pkoppula@13316 1486 if (m.group(4) != null) {
pkoppula@13316 1487 result += 3600 * Integer.parseInt(m.group(4));
pkoppula@13316 1488 }
pkoppula@13316 1489 if (m.group(6) != null) {
pkoppula@13316 1490 result += 60 * Integer.parseInt(m.group(6));
pkoppula@13316 1491 }
pkoppula@13316 1492 if (m.group(8) != null) {
pkoppula@13316 1493 result += Integer.parseInt(m.group(8));
pkoppula@13316 1494 }
pkoppula@13316 1495 return result;
pkoppula@13316 1496 }
pkoppula@13316 1497
pkoppula@13316 1498 throw new KrbException("Illegal duration format " + s);
pkoppula@13316 1499 }
pkoppula@13316 1500
andrew@13817 1501 private int[] filterSupported(int[] input) {
andrew@13817 1502 int count = 0;
andrew@13817 1503 for (int i = 0; i < input.length; i++) {
andrew@13817 1504 if (!EType.isSupported(input[i])) {
andrew@13817 1505 continue;
andrew@13817 1506 }
andrew@13817 1507 if (SUPPORTED_ETYPES != null) {
andrew@13817 1508 boolean supported = false;
andrew@13817 1509 for (String se : SUPPORTED_ETYPES.split(",")) {
andrew@13817 1510 if (Config.getType(se) == input[i]) {
andrew@13817 1511 supported = true;
andrew@13817 1512 break;
andrew@13817 1513 }
andrew@13817 1514 }
andrew@13817 1515 if (!supported) {
andrew@13817 1516 continue;
andrew@13817 1517 }
andrew@13817 1518 }
andrew@13817 1519 if (count != i) {
andrew@13817 1520 input[count] = input[i];
andrew@13817 1521 }
andrew@13817 1522 count++;
andrew@13817 1523 }
andrew@13817 1524 if (count != input.length) {
andrew@13817 1525 input = Arrays.copyOf(input, count);
andrew@13817 1526 }
andrew@13817 1527 return input;
andrew@13817 1528 }
andrew@13817 1529
pkoppula@13316 1530 /**
weijun@681 1531 * Generates a line for a KDC to put inside [realms] of krb5.conf
weijun@9656 1532 * @return REALM.NAME = { kdc = host:port etc }
weijun@681 1533 */
weijun@9656 1534 private String realmLine() {
weijun@9656 1535 StringBuilder sb = new StringBuilder();
weijun@9656 1536 sb.append(realm).append(" = {\n kdc = ")
weijun@9656 1537 .append(kdc).append(':').append(port).append('\n');
weijun@9656 1538 for (String s: conf) {
weijun@9656 1539 sb.append(" ").append(s).append('\n');
weijun@9656 1540 }
weijun@9656 1541 return sb.append("}\n").toString();
weijun@681 1542 }
weijun@681 1543
weijun@681 1544 /**
weijun@681 1545 * Start the KDC service. This server listens on both UDP and TCP using
weijun@681 1546 * the same port number. It uses three threads to deal with requests.
weijun@681 1547 * They can be set to daemon threads if requested.
weijun@681 1548 * @param port the port number to listen to. If zero, a random available
weijun@681 1549 * port no less than 8000 will be chosen and used.
weijun@681 1550 * @param asDaemon true if the KDC threads should be daemons
weijun@681 1551 * @throws java.io.IOException for any communication error
weijun@681 1552 */
weijun@681 1553 protected void startServer(int port, boolean asDaemon) throws IOException {
andrew@13817 1554 if (nativeKdc != null) {
andrew@13817 1555 startNativeServer(port, asDaemon);
andrew@13817 1556 } else {
andrew@13817 1557 startJavaServer(port, asDaemon);
andrew@13817 1558 }
andrew@13817 1559 }
andrew@13817 1560
andrew@13817 1561 private void startNativeServer(int port, boolean asDaemon) throws IOException {
andrew@13817 1562 nativeKdc.prepare();
andrew@13817 1563 nativeKdc.init();
andrew@13817 1564 kdcProc = nativeKdc.kdc();
andrew@13817 1565 }
andrew@13817 1566
andrew@13817 1567 private void startJavaServer(int port, boolean asDaemon) throws IOException {
weijun@681 1568 if (port > 0) {
weijun@681 1569 u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1"));
weijun@681 1570 t1 = new ServerSocket(port);
weijun@681 1571 } else {
weijun@681 1572 while (true) {
weijun@681 1573 // Try to find a port number that's both TCP and UDP free
weijun@681 1574 try {
weijun@681 1575 port = 8000 + new java.util.Random().nextInt(10000);
weijun@681 1576 u1 = null;
weijun@681 1577 u1 = new DatagramSocket(port, InetAddress.getByName("127.0.0.1"));
weijun@681 1578 t1 = new ServerSocket(port);
weijun@681 1579 break;
weijun@681 1580 } catch (Exception e) {
weijun@681 1581 if (u1 != null) u1.close();
weijun@681 1582 }
weijun@681 1583 }
weijun@681 1584 }
weijun@681 1585 final DatagramSocket udp = u1;
weijun@681 1586 final ServerSocket tcp = t1;
weijun@681 1587 System.out.println("Start KDC on " + port);
weijun@681 1588
weijun@681 1589 this.port = port;
weijun@681 1590
weijun@681 1591 // The UDP consumer
weijun@2037 1592 thread1 = new Thread() {
weijun@681 1593 public void run() {
msolovie@11701 1594 udpConsumerReady = true;
weijun@681 1595 while (true) {
weijun@681 1596 try {
weijun@681 1597 byte[] inbuf = new byte[8192];
weijun@681 1598 DatagramPacket p = new DatagramPacket(inbuf, inbuf.length);
weijun@681 1599 udp.receive(p);
weijun@681 1600 System.out.println("-----------------------------------------------");
weijun@681 1601 System.out.println(">>>>> UDP packet received");
weijun@681 1602 q.put(new Job(processMessage(Arrays.copyOf(inbuf, p.getLength())), udp, p));
weijun@681 1603 } catch (Exception e) {
weijun@681 1604 e.printStackTrace();
weijun@681 1605 }
weijun@681 1606 }
weijun@681 1607 }
weijun@681 1608 };
weijun@2037 1609 thread1.setDaemon(asDaemon);
weijun@2037 1610 thread1.start();
weijun@681 1611
weijun@681 1612 // The TCP consumer
weijun@2037 1613 thread2 = new Thread() {
weijun@681 1614 public void run() {
msolovie@11701 1615 tcpConsumerReady = true;
weijun@681 1616 while (true) {
weijun@681 1617 try {
weijun@681 1618 Socket socket = tcp.accept();
weijun@681 1619 System.out.println("-----------------------------------------------");
weijun@681 1620 System.out.println(">>>>> TCP connection established");
weijun@681 1621 DataInputStream in = new DataInputStream(socket.getInputStream());
weijun@681 1622 DataOutputStream out = new DataOutputStream(socket.getOutputStream());
weijun@681 1623 byte[] token = new byte[in.readInt()];
weijun@681 1624 in.readFully(token);
weijun@681 1625 q.put(new Job(processMessage(token), socket, out));
weijun@681 1626 } catch (Exception e) {
weijun@681 1627 e.printStackTrace();
weijun@681 1628 }
weijun@681 1629 }
weijun@681 1630 }
weijun@681 1631 };
weijun@2037 1632 thread2.setDaemon(asDaemon);
weijun@2037 1633 thread2.start();
weijun@681 1634
weijun@681 1635 // The dispatcher
weijun@2037 1636 thread3 = new Thread() {
weijun@681 1637 public void run() {
msolovie@11701 1638 dispatcherReady = true;
weijun@681 1639 while (true) {
weijun@681 1640 try {
weijun@681 1641 q.take().send();
weijun@681 1642 } catch (Exception e) {
weijun@681 1643 }
weijun@681 1644 }
weijun@681 1645 }
weijun@681 1646 };
weijun@2037 1647 thread3.setDaemon(true);
weijun@2037 1648 thread3.start();
msolovie@11701 1649
msolovie@11701 1650 // wait for the KDC is ready
msolovie@11701 1651 try {
msolovie@11701 1652 while (!isReady()) {
msolovie@11701 1653 Thread.sleep(100);
msolovie@11701 1654 }
msolovie@11701 1655 } catch(InterruptedException e) {
msolovie@11701 1656 throw new IOException(e);
msolovie@11701 1657 }
msolovie@11701 1658 }
msolovie@11701 1659
andrew@13817 1660 public void kinit(String user, String ccache) throws Exception {
andrew@13817 1661 if (user.indexOf('@') < 0) {
andrew@13817 1662 user = user + "@" + realm;
andrew@13817 1663 }
andrew@13817 1664 if (nativeKdc != null) {
andrew@13817 1665 nativeKdc.kinit(user, ccache);
andrew@13817 1666 } else {
andrew@13817 1667 Context.fromUserPass(user, passwords.get(user), false)
andrew@13817 1668 .ccache(ccache);
andrew@13817 1669 }
andrew@13817 1670 }
andrew@13817 1671
msolovie@11701 1672 boolean isReady() {
msolovie@11701 1673 return udpConsumerReady && tcpConsumerReady && dispatcherReady;
weijun@681 1674 }
weijun@681 1675
weijun@2037 1676 public void terminate() {
andrew@13817 1677 if (nativeKdc != null) {
andrew@13817 1678 System.out.println("Killing kdc...");
andrew@13817 1679 kdcProc.destroyForcibly();
andrew@13817 1680 System.out.println("Done");
andrew@13817 1681 } else {
andrew@13817 1682 try {
andrew@13817 1683 thread1.stop();
andrew@13817 1684 thread2.stop();
andrew@13817 1685 thread3.stop();
andrew@13817 1686 u1.close();
andrew@13817 1687 t1.close();
andrew@13817 1688 } catch (Exception e) {
andrew@13817 1689 // OK
andrew@13817 1690 }
weijun@2037 1691 }
weijun@2037 1692 }
weijun@11706 1693
asmotrak@11718 1694 public static KDC startKDC(final String host, final String krbConfFileName,
weijun@11706 1695 final String realm, final Map<String, String> principals,
weijun@11706 1696 final String ktab, final KtabMode mode) {
weijun@11706 1697
asmotrak@11718 1698 KDC kdc;
weijun@11706 1699 try {
asmotrak@11718 1700 kdc = KDC.create(realm, host, 0, true);
weijun@11706 1701 kdc.setOption(KDC.Option.PREAUTH_REQUIRED, Boolean.FALSE);
asmotrak@11718 1702 if (krbConfFileName != null) {
asmotrak@11718 1703 KDC.saveConfig(krbConfFileName, kdc);
asmotrak@11718 1704 }
weijun@11706 1705
weijun@11706 1706 // Add principals
weijun@11706 1707 if (principals != null) {
weijun@11706 1708 principals.forEach((name, password) -> {
weijun@11706 1709 if (password == null || password.isEmpty()) {
weijun@11706 1710 System.out.println(String.format(
weijun@11706 1711 "KDC:add a principal '%s' with a random " +
weijun@11706 1712 "password", name));
weijun@11706 1713 kdc.addPrincipalRandKey(name);
weijun@11706 1714 } else {
weijun@11706 1715 System.out.println(String.format(
weijun@11706 1716 "KDC:add a principal '%s' with '%s' password",
weijun@11706 1717 name, password));
weijun@11706 1718 kdc.addPrincipal(name, password.toCharArray());
weijun@11706 1719 }
weijun@11706 1720 });
weijun@11706 1721 }
weijun@11706 1722
weijun@11706 1723 // Create or append keys to existing keytab file
weijun@11706 1724 if (ktab != null) {
weijun@11706 1725 File ktabFile = new File(ktab);
weijun@11706 1726 switch(mode) {
weijun@11706 1727 case APPEND:
weijun@11706 1728 if (ktabFile.exists()) {
weijun@11706 1729 System.out.println(String.format(
weijun@11706 1730 "KDC:append keys to an exising keytab "
weijun@11706 1731 + "file %s", ktab));
weijun@11706 1732 kdc.appendKtab(ktab);
weijun@11706 1733 } else {
weijun@11706 1734 System.out.println(String.format(
weijun@11706 1735 "KDC:create a new keytab file %s", ktab));
weijun@11706 1736 kdc.writeKtab(ktab);
weijun@11706 1737 }
weijun@11706 1738 break;
weijun@11706 1739 case EXISTING:
weijun@11706 1740 System.out.println(String.format(
weijun@11706 1741 "KDC:use an existing keytab file %s", ktab));
weijun@11706 1742 break;
weijun@11706 1743 default:
weijun@11706 1744 throw new RuntimeException(String.format(
weijun@11706 1745 "KDC:unsupported keytab mode: %s", mode));
weijun@11706 1746 }
weijun@11706 1747 }
weijun@11706 1748
weijun@11706 1749 System.out.println(String.format(
weijun@11706 1750 "KDC: started on %s:%s with '%s' realm",
weijun@11706 1751 host, kdc.getPort(), realm));
weijun@11706 1752 } catch (Exception e) {
weijun@11706 1753 throw new RuntimeException("KDC: unexpected exception", e);
weijun@11706 1754 }
weijun@11706 1755
asmotrak@11718 1756 return kdc;
weijun@11706 1757 }
weijun@11706 1758
weijun@681 1759 /**
weijun@681 1760 * Helper class to encapsulate a job in a KDC.
weijun@681 1761 */
weijun@681 1762 private static class Job {
weijun@681 1763 byte[] token; // The received request at creation time and
weijun@681 1764 // the response at send time
weijun@681 1765 Socket s; // The TCP socket from where the request comes
weijun@681 1766 DataOutputStream out; // The OutputStream of the TCP socket
weijun@681 1767 DatagramSocket s2; // The UDP socket from where the request comes
weijun@681 1768 DatagramPacket dp; // The incoming UDP datagram packet
weijun@681 1769 boolean useTCP; // Whether TCP or UDP is used
weijun@681 1770
weijun@681 1771 // Creates a job object for TCP
weijun@681 1772 Job(byte[] token, Socket s, DataOutputStream out) {
weijun@681 1773 useTCP = true;
weijun@681 1774 this.token = token;
weijun@681 1775 this.s = s;
weijun@681 1776 this.out = out;
weijun@681 1777 }
weijun@681 1778
weijun@681 1779 // Creates a job object for UDP
weijun@681 1780 Job(byte[] token, DatagramSocket s2, DatagramPacket dp) {
weijun@681 1781 useTCP = false;
weijun@681 1782 this.token = token;
weijun@681 1783 this.s2 = s2;
weijun@681 1784 this.dp = dp;
weijun@681 1785 }
weijun@681 1786
weijun@681 1787 // Sends the output back to the client
weijun@681 1788 void send() {
weijun@681 1789 try {
weijun@681 1790 if (useTCP) {
weijun@681 1791 System.out.println(">>>>> TCP request honored");
weijun@681 1792 out.writeInt(token.length);
weijun@681 1793 out.write(token);
weijun@681 1794 s.close();
weijun@681 1795 } else {
weijun@681 1796 System.out.println(">>>>> UDP request honored");
weijun@681 1797 s2.send(new DatagramPacket(token, token.length, dp.getAddress(), dp.getPort()));
weijun@681 1798 }
weijun@681 1799 } catch (Exception e) {
weijun@681 1800 e.printStackTrace();
weijun@681 1801 }
weijun@681 1802 }
weijun@681 1803 }
weijun@1303 1804
weijun@1303 1805 public static class KDCNameService implements NameServiceDescriptor {
asmotrak@11718 1806
asmotrak@11718 1807 public static String NOT_EXISTING_HOST = "not.existing.host";
asmotrak@11718 1808
weijun@1303 1809 @Override
weijun@1303 1810 public NameService createNameService() throws Exception {
weijun@1303 1811 NameService ns = new NameService() {
weijun@1303 1812 @Override
weijun@1303 1813 public InetAddress[] lookupAllHostAddr(String host)
weijun@1303 1814 throws UnknownHostException {
asmotrak@11718 1815 // Everything is localhost except NOT_EXISTING_HOST
asmotrak@11718 1816 if (NOT_EXISTING_HOST.equals(host)) {
asmotrak@11718 1817 throw new UnknownHostException("Unknown host name: "
asmotrak@11718 1818 + NOT_EXISTING_HOST);
asmotrak@11718 1819 }
weijun@1303 1820 return new InetAddress[]{
weijun@1303 1821 InetAddress.getByAddress(host, new byte[]{127,0,0,1})
weijun@1303 1822 };
weijun@1303 1823 }
weijun@1303 1824 @Override
weijun@1303 1825 public String getHostByAddr(byte[] addr)
weijun@1303 1826 throws UnknownHostException {
weijun@1303 1827 // No reverse lookup, PrincipalName use original string
weijun@1303 1828 throw new UnknownHostException();
weijun@1303 1829 }
weijun@1303 1830 };
weijun@1303 1831 return ns;
weijun@1303 1832 }
weijun@1303 1833
weijun@1303 1834 @Override
weijun@1303 1835 public String getProviderName() {
weijun@1303 1836 return "mock";
weijun@1303 1837 }
weijun@1303 1838
weijun@1303 1839 @Override
weijun@1303 1840 public String getType() {
weijun@1303 1841 return "ns";
weijun@1303 1842 }
weijun@1303 1843 }
weijun@3057 1844
andrew@13817 1845 /**
andrew@13817 1846 * A native KDC using the binaries in nativePath. Attention:
andrew@13817 1847 * this is using binaries, not an existing KDC instance.
andrew@13817 1848 * An implementation of this takes care of configuration,
andrew@13817 1849 * principal db managing and KDC startup.
andrew@13817 1850 */
andrew@13817 1851 static abstract class NativeKdc {
andrew@13817 1852
andrew@13817 1853 protected Map<String,String> env;
andrew@13817 1854 protected String nativePath;
andrew@13817 1855 protected String base;
andrew@13817 1856 protected String realm;
andrew@13817 1857 protected int port;
andrew@13817 1858
andrew@13817 1859 NativeKdc(String nativePath, KDC kdc) {
andrew@13817 1860 if (kdc.port == 0) {
andrew@13817 1861 kdc.port = 8000 + new java.util.Random().nextInt(10000);
andrew@13817 1862 }
andrew@13817 1863 this.nativePath = nativePath;
andrew@13817 1864 this.realm = kdc.realm;
andrew@13817 1865 this.port = kdc.port;
andrew@13817 1866 this.base = Paths.get("" + port).toAbsolutePath().toString();
andrew@13817 1867 }
andrew@13817 1868
andrew@13817 1869 // Add a new principal
andrew@13817 1870 abstract void addPrincipal(String user, String pass);
andrew@13817 1871 // Add a keytab entry
andrew@13817 1872 abstract void ktadd(String user, String ktab);
andrew@13817 1873 // Initialize KDC
andrew@13817 1874 abstract void init();
andrew@13817 1875 // Start kdc
andrew@13817 1876 abstract Process kdc();
andrew@13817 1877 // Configuration
andrew@13817 1878 abstract void prepare();
andrew@13817 1879 // Fill ccache
andrew@13817 1880 abstract void kinit(String user, String ccache);
andrew@13817 1881
andrew@13817 1882 static NativeKdc get(KDC kdc) {
andrew@13817 1883 String prop = System.getProperty("native.kdc.path");
andrew@13817 1884 if (prop == null) {
andrew@13817 1885 return null;
andrew@13817 1886 } else if (Files.exists(Paths.get(prop, "sbin/krb5kdc"))) {
andrew@13817 1887 return new MIT(true, prop, kdc);
andrew@13817 1888 } else if (Files.exists(Paths.get(prop, "kdc/krb5kdc"))) {
andrew@13817 1889 return new MIT(false, prop, kdc);
andrew@13817 1890 } else if (Files.exists(Paths.get(prop, "libexec/kdc"))) {
andrew@13817 1891 return new Heimdal(prop, kdc);
andrew@13817 1892 } else {
andrew@13817 1893 throw new IllegalArgumentException("Strange " + prop);
andrew@13817 1894 }
andrew@13817 1895 }
andrew@13817 1896
andrew@13817 1897 Process run(boolean wait, String... cmd) {
andrew@13817 1898 try {
andrew@13817 1899 System.out.println("Running " + cmd2str(env, cmd));
andrew@13817 1900 ProcessBuilder pb = new ProcessBuilder();
andrew@13817 1901 pb.inheritIO();
andrew@13817 1902 pb.environment().putAll(env);
andrew@13817 1903 Process p = pb.command(cmd).start();
andrew@13817 1904 if (wait) {
andrew@13817 1905 if (p.waitFor() < 0) {
andrew@13817 1906 throw new RuntimeException("exit code is not null");
andrew@13817 1907 }
andrew@13817 1908 return null;
andrew@13817 1909 } else {
andrew@13817 1910 return p;
andrew@13817 1911 }
andrew@13817 1912 } catch (Exception e) {
andrew@13817 1913 throw new RuntimeException(e);
andrew@13817 1914 }
andrew@13817 1915 }
andrew@13817 1916
andrew@13817 1917 private String cmd2str(Map<String,String> env, String... cmd) {
andrew@13817 1918 return env.entrySet().stream().map(e -> e.getKey()+"="+e.getValue())
andrew@13817 1919 .collect(Collectors.joining(" ")) + " " +
andrew@13817 1920 Stream.of(cmd).collect(Collectors.joining(" "));
andrew@13817 1921 }
andrew@13817 1922 }
andrew@13817 1923
andrew@13817 1924 // Heimdal KDC. Build your own and run "make install" to nativePath.
andrew@13817 1925 static class Heimdal extends NativeKdc {
andrew@13817 1926
andrew@13817 1927 Heimdal(String nativePath, KDC kdc) {
andrew@13817 1928 super(nativePath, kdc);
andrew@13817 1929 Map<String, String> environment = new HashMap<>();
andrew@13817 1930 environment.put("KRB5_CONFIG", base + "/krb5.conf");
andrew@13817 1931 environment.put("KRB5_TRACE", "/dev/stderr");
andrew@13817 1932 environment.put("DYLD_LIBRARY_PATH", nativePath + "/lib");
andrew@13817 1933 environment.put("LD_LIBRARY_PATH", nativePath + "/lib");
andrew@13817 1934 this.env = Collections.unmodifiableMap(environment);
andrew@13817 1935 }
andrew@13817 1936
andrew@13817 1937 @Override
andrew@13817 1938 public void addPrincipal(String user, String pass) {
andrew@13817 1939 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,
andrew@13817 1940 "add", "-p", pass, "--use-defaults", user);
andrew@13817 1941 }
andrew@13817 1942
andrew@13817 1943 @Override
andrew@13817 1944 public void ktadd(String user, String ktab) {
andrew@13817 1945 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,
andrew@13817 1946 "ext_keytab", "-k", ktab, user);
andrew@13817 1947 }
andrew@13817 1948
andrew@13817 1949 @Override
andrew@13817 1950 public void init() {
andrew@13817 1951 run(true, nativePath + "/bin/kadmin", "-l", "-r", realm,
andrew@13817 1952 "init", "--realm-max-ticket-life=1day",
andrew@13817 1953 "--realm-max-renewable-life=1month", realm);
andrew@13817 1954 }
andrew@13817 1955
andrew@13817 1956 @Override
andrew@13817 1957 public Process kdc() {
andrew@13817 1958 return run(false, nativePath + "/libexec/kdc",
andrew@13817 1959 "--addresses=127.0.0.1", "-P", "" + port);
andrew@13817 1960 }
andrew@13817 1961
andrew@13817 1962 @Override
andrew@13817 1963 public void prepare() {
andrew@13817 1964 try {
andrew@13817 1965 Files.createDirectory(Paths.get(base));
andrew@13817 1966 Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList(
andrew@13817 1967 "[libdefaults]",
andrew@13817 1968 "default_realm = " + realm,
andrew@13817 1969 "default_keytab_name = FILE:" + base + "/krb5.keytab",
andrew@13817 1970 "forwardable = true",
andrew@13817 1971 "dns_lookup_kdc = no",
andrew@13817 1972 "dns_lookup_realm = no",
andrew@13817 1973 "dns_canonicalize_hostname = false",
andrew@13817 1974 "\n[realms]",
andrew@13817 1975 realm + " = {",
andrew@13817 1976 " kdc = localhost:" + port,
andrew@13817 1977 "}",
andrew@13817 1978 "\n[kdc]",
andrew@13817 1979 "db-dir = " + base,
andrew@13817 1980 "database = {",
andrew@13817 1981 " label = {",
andrew@13817 1982 " dbname = " + base + "/current-db",
andrew@13817 1983 " realm = " + realm,
andrew@13817 1984 " mkey_file = " + base + "/mkey.file",
andrew@13817 1985 " acl_file = " + base + "/heimdal.acl",
andrew@13817 1986 " log_file = " + base + "/current.log",
andrew@13817 1987 " }",
andrew@13817 1988 "}",
andrew@13817 1989 SUPPORTED_ETYPES == null ? ""
andrew@13817 1990 : ("\n[kadmin]\ndefault_keys = "
andrew@13817 1991 + (SUPPORTED_ETYPES + ",")
andrew@13817 1992 .replaceAll(",", ":pw-salt ")),
andrew@13817 1993 "\n[logging]",
andrew@13817 1994 "kdc = 0-/FILE:" + base + "/messages.log",
andrew@13817 1995 "krb5 = 0-/FILE:" + base + "/messages.log",
andrew@13817 1996 "default = 0-/FILE:" + base + "/messages.log"
andrew@13817 1997 ));
andrew@13817 1998 } catch (IOException e) {
andrew@13817 1999 throw new UncheckedIOException(e);
andrew@13817 2000 }
andrew@13817 2001 }
andrew@13817 2002
andrew@13817 2003 @Override
andrew@13817 2004 void kinit(String user, String ccache) {
andrew@13817 2005 String tmpName = base + "/" + user + "." +
andrew@13817 2006 System.identityHashCode(this) + ".keytab";
andrew@13817 2007 ktadd(user, tmpName);
andrew@13817 2008 run(true, nativePath + "/bin/kinit",
andrew@13817 2009 "-f", "-t", tmpName, "-c", ccache, user);
andrew@13817 2010 }
andrew@13817 2011 }
andrew@13817 2012
andrew@13817 2013 // MIT krb5 KDC. Make your own exploded (install == false), or
andrew@13817 2014 // "make install" into nativePath (install == true).
andrew@13817 2015 static class MIT extends NativeKdc {
andrew@13817 2016
andrew@13817 2017 private boolean install; // "make install" or "make"
andrew@13817 2018
andrew@13817 2019 MIT(boolean install, String nativePath, KDC kdc) {
andrew@13817 2020 super(nativePath, kdc);
andrew@13817 2021 this.install = install;
andrew@13817 2022 Map<String, String> environment = new HashMap<>();
andrew@13817 2023 environment.put("KRB5_KDC_PROFILE", base + "/kdc.conf");
andrew@13817 2024 environment.put("KRB5_CONFIG", base + "/krb5.conf");
andrew@13817 2025 environment.put("KRB5_TRACE", "/dev/stderr");
andrew@13817 2026 environment.put("DYLD_LIBRARY_PATH", nativePath + "/lib");
andrew@13817 2027 environment.put("LD_LIBRARY_PATH", nativePath + "/lib");
andrew@13817 2028 this.env = Collections.unmodifiableMap(environment);
andrew@13817 2029 }
andrew@13817 2030
andrew@13817 2031 @Override
andrew@13817 2032 public void addPrincipal(String user, String pass) {
andrew@13817 2033 run(true, nativePath +
andrew@13817 2034 (install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local",
andrew@13817 2035 "-q", "addprinc -pw " + pass + " " + user);
andrew@13817 2036 }
andrew@13817 2037
andrew@13817 2038 @Override
andrew@13817 2039 public void ktadd(String user, String ktab) {
andrew@13817 2040 run(true, nativePath +
andrew@13817 2041 (install ? "/sbin/" : "/kadmin/cli/") + "kadmin.local",
andrew@13817 2042 "-q", "ktadd -k " + ktab + " -norandkey " + user);
andrew@13817 2043 }
andrew@13817 2044
andrew@13817 2045 @Override
andrew@13817 2046 public void init() {
andrew@13817 2047 run(true, nativePath +
andrew@13817 2048 (install ? "/sbin/" : "/kadmin/dbutil/") + "kdb5_util",
andrew@13817 2049 "create", "-s", "-W", "-P", "olala");
andrew@13817 2050 }
andrew@13817 2051
andrew@13817 2052 @Override
andrew@13817 2053 public Process kdc() {
andrew@13817 2054 return run(false, nativePath +
andrew@13817 2055 (install ? "/sbin/" : "/kdc/") + "krb5kdc",
andrew@13817 2056 "-n");
andrew@13817 2057 }
andrew@13817 2058
andrew@13817 2059 @Override
andrew@13817 2060 public void prepare() {
andrew@13817 2061 try {
andrew@13817 2062 Files.createDirectory(Paths.get(base));
andrew@13817 2063 Files.write(Paths.get(base + "/kdc.conf"), Arrays.asList(
andrew@13817 2064 "[kdcdefaults]",
andrew@13817 2065 "\n[realms]",
andrew@13817 2066 realm + "= {",
andrew@13817 2067 " kdc_listen = " + this.port,
andrew@13817 2068 " kdc_tcp_listen = " + this.port,
andrew@13817 2069 " database_name = " + base + "/principal",
andrew@13817 2070 " key_stash_file = " + base + "/.k5.ATHENA.MIT.EDU",
andrew@13817 2071 SUPPORTED_ETYPES == null ? ""
andrew@13817 2072 : (" supported_enctypes = "
andrew@13817 2073 + (SUPPORTED_ETYPES + ",")
andrew@13817 2074 .replaceAll(",", ":normal ")),
andrew@13817 2075 "}"
andrew@13817 2076 ));
andrew@13817 2077 Files.write(Paths.get(base + "/krb5.conf"), Arrays.asList(
andrew@13817 2078 "[libdefaults]",
andrew@13817 2079 "default_realm = " + realm,
andrew@13817 2080 "default_keytab_name = FILE:" + base + "/krb5.keytab",
andrew@13817 2081 "forwardable = true",
andrew@13817 2082 "dns_lookup_kdc = no",
andrew@13817 2083 "dns_lookup_realm = no",
andrew@13817 2084 "dns_canonicalize_hostname = false",
andrew@13817 2085 "\n[realms]",
andrew@13817 2086 realm + " = {",
andrew@13817 2087 " kdc = localhost:" + port,
andrew@13817 2088 "}",
andrew@13817 2089 "\n[logging]",
andrew@13817 2090 "kdc = FILE:" + base + "/krb5kdc.log"
andrew@13817 2091 ));
andrew@13817 2092 } catch (IOException e) {
andrew@13817 2093 throw new UncheckedIOException(e);
andrew@13817 2094 }
andrew@13817 2095 }
andrew@13817 2096
andrew@13817 2097 @Override
andrew@13817 2098 void kinit(String user, String ccache) {
andrew@13817 2099 String tmpName = base + "/" + user + "." +
andrew@13817 2100 System.identityHashCode(this) + ".keytab";
andrew@13817 2101 ktadd(user, tmpName);
andrew@13817 2102 run(true, nativePath +
andrew@13817 2103 (install ? "/bin/" : "/clients/kinit/") + "kinit",
andrew@13817 2104 "-f", "-t", tmpName, "-c", ccache, user);
andrew@13817 2105 }
andrew@13817 2106 }
andrew@13817 2107
weijun@3057 2108 // Calling private methods thru reflections
weijun@3057 2109 private static final Field getEType;
weijun@3057 2110 private static final Constructor<EncryptedData> ctorEncryptedData;
weijun@3057 2111 private static final Method stringToKey;
weijun@6093 2112 private static final Field getAddlTkt;
weijun@3057 2113
weijun@3057 2114 static {
weijun@3057 2115 try {
weijun@3057 2116 ctorEncryptedData = EncryptedData.class.getDeclaredConstructor(DerValue.class);
weijun@3057 2117 ctorEncryptedData.setAccessible(true);
weijun@3057 2118 getEType = KDCReqBody.class.getDeclaredField("eType");
weijun@3057 2119 getEType.setAccessible(true);
weijun@3057 2120 stringToKey = EncryptionKey.class.getDeclaredMethod(
weijun@3057 2121 "stringToKey",
weijun@3057 2122 char[].class, String.class, byte[].class, Integer.TYPE);
weijun@3057 2123 stringToKey.setAccessible(true);
weijun@6093 2124 getAddlTkt = KDCReqBody.class.getDeclaredField("additionalTickets");
weijun@6093 2125 getAddlTkt.setAccessible(true);
weijun@3057 2126 } catch (NoSuchFieldException nsfe) {
weijun@3057 2127 throw new AssertionError(nsfe);
weijun@3057 2128 } catch (NoSuchMethodException nsme) {
weijun@3057 2129 throw new AssertionError(nsme);
weijun@3057 2130 }
weijun@3057 2131 }
weijun@3057 2132 private EncryptedData newEncryptedData(DerValue der) {
weijun@3057 2133 try {
weijun@3057 2134 return ctorEncryptedData.newInstance(der);
weijun@3057 2135 } catch (Exception e) {
weijun@3057 2136 throw new AssertionError(e);
weijun@3057 2137 }
weijun@3057 2138 }
weijun@3057 2139 private static int[] KDCReqBodyDotEType(KDCReqBody body) {
weijun@3057 2140 try {
weijun@3057 2141 return (int[]) getEType.get(body);
weijun@3057 2142 } catch (Exception e) {
weijun@3057 2143 throw new AssertionError(e);
weijun@3057 2144 }
weijun@3057 2145 }
weijun@3057 2146 private static byte[] EncryptionKeyDotStringToKey(char[] password, String salt,
weijun@3057 2147 byte[] s2kparams, int keyType) throws KrbCryptoException {
weijun@3057 2148 try {
weijun@3057 2149 return (byte[])stringToKey.invoke(
weijun@3057 2150 null, password, salt, s2kparams, keyType);
weijun@3057 2151 } catch (InvocationTargetException ex) {
weijun@3057 2152 throw (KrbCryptoException)ex.getCause();
weijun@3057 2153 } catch (Exception e) {
weijun@3057 2154 throw new AssertionError(e);
weijun@3057 2155 }
weijun@3057 2156 }
weijun@6093 2157 private static Ticket KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body) {
weijun@6093 2158 try {
weijun@6093 2159 return ((Ticket[])getAddlTkt.get(body))[0];
weijun@6093 2160 } catch (Exception e) {
weijun@6093 2161 throw new AssertionError(e);
weijun@6093 2162 }
weijun@6093 2163 }
weijun@681 2164 }

mercurial