Sun, 31 May 2020 10:13:04 +0800
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 | } |