Showing the changes to java/pkg/com/sun/security/auth/module/Krb5LoginModule.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,1225 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | |||
| 27 | package com.sun.security.auth.module; | ||
| 28 | |||
| 29 | import java.io.*; | ||
| 30 | import java.text.MessageFormat; | ||
| 31 | import java.util.*; | ||
| 32 | |||
| 33 | import javax.security.auth.*; | ||
| 34 | import javax.security.auth.kerberos.*; | ||
| 35 | import javax.security.auth.callback.*; | ||
| 36 | import javax.security.auth.login.*; | ||
| 37 | import javax.security.auth.spi.*; | ||
| 38 | |||
| 39 | import sun.security.krb5.*; | ||
| 40 | import sun.security.jgss.krb5.Krb5Util; | ||
| 41 | import sun.security.krb5.Credentials; | ||
| 42 | import sun.misc.HexDumpEncoder; | ||
| 43 | |||
| 44 | /** | ||
| 45 | * <p> This <code>LoginModule</code> authenticates users using | ||
| 46 | * Kerberos protocols. | ||
| 47 | * | ||
| 48 | * <p> The configuration entry for <code>Krb5LoginModule</code> has | ||
| 49 | * several options that control the authentication process and | ||
| 50 | * additions to the <code>Subject</code>'s private credential | ||
| 51 | * set. Irrespective of these options, the <code>Subject</code>'s | ||
| 52 | * principal set and private credentials set are updated only when | ||
| 53 | * <code>commit</code> is called. | ||
| 54 | * When <code>commit</code> is called, the <code>KerberosPrincipal</code> | ||
| 55 | * is added to the <code>Subject</code>'s | ||
| 56 | * principal set and <code>KerberosTicket</code> is | ||
| 57 | * added to the <code>Subject</code>'s private credentials. | ||
| 58 | * | ||
| 59 | * <p> If the configuration entry for <code>KerberosLoginModule</code> | ||
| 60 | * has the option <code>storeKey</code> set to true, then | ||
| 61 | * <code>KerberosKey</code> will also be added to the | ||
| 62 | * subject's private credentials. <code>KerberosKey</code>, the principal's | ||
| 63 | * key will be either obtained from the keytab or | ||
| 64 | * derived from user's password. | ||
| 65 | * | ||
| 66 | * <p> This <code>LoginModule</code> recognizes the <code>doNotPrompt</code> | ||
| 67 | * option. If set to true the user will not be prompted for the password. | ||
| 68 | * | ||
| 69 | * <p> The user can specify the location of the ticket cache by using | ||
| 70 | * the option <code>ticketCache</code> in the configuration entry. | ||
| 71 | * | ||
| 72 | * <p>The user can specify the keytab location by using | ||
| 73 | * the option <code>keyTab</code> | ||
| 74 | * in the configuration entry. | ||
| 75 | * | ||
| 76 | * <p> The principal name can be specified in the configuration entry | ||
| 77 | * by using the option <code>principal</code>. The principal name | ||
| 78 | * can either be a simple user name or a service name such as | ||
| 79 | * <code>host/mission.eng.sun.com</code>. The principal can also | ||
| 80 | * be set using the system property <code>sun.security.krb5.principal</code>. | ||
| 81 | * This property is checked during login. If this property is not set, then | ||
| 82 | * the principal name from the configuration is used. In the | ||
| 83 | * case where the principal property is not set and the principal | ||
| 84 | * entry also does not exist, the user is prompted for the name. | ||
| 85 | * When this property of entry is set, and <code>useTicketCache</code> | ||
| 86 | * is set to true, only TGT belonging to this principal is used. | ||
| 87 | * | ||
| 88 | * <p> The following is a list of configuration options supported | ||
| 89 | * for <code>Krb5LoginModule</code>: | ||
| 90 | * <dl> | ||
| 91 | * <blockquote><dt><b><code>refreshKrb5Config</code></b>:</dt> | ||
| 92 | * <dd> Set this to true, if you want the configuration | ||
| 93 | * to be refreshed before the <code>login</code> method is called.</dd> | ||
| 94 | * <P> | ||
| 95 | * <dt><b><code>useTicketCache</code></b>:</dt> | ||
| 96 | * <dd>Set this to true, if you want the | ||
| 97 | * TGT to be obtained | ||
| 98 | * from the ticket cache. Set this option | ||
| 99 | * to false if you do not want this module to use the ticket cache. | ||
| 100 | * (Default is False). | ||
| 101 | * This module will | ||
| 102 | * search for the ticket | ||
| 103 | * cache in the following locations: | ||
| 104 | * On Solaris and Linux | ||
| 105 | * it will look for the ticket cache in /tmp/krb5cc_<code>uid</code> | ||
| 106 | * where the uid is numeric user | ||
| 107 | * identifier. If the ticket cache is | ||
| 108 | * not available in the above location, or if we are on a | ||
| 109 | * Windows platform, it will look for the cache as | ||
| 110 | * {user.home}{file.separator}krb5cc_{user.name}. | ||
| 111 | * You can override the ticket cache location by using | ||
| 112 | * <code>ticketCache</code>. | ||
| 113 | * For Windows, if a ticket cannot be retrieved from the file ticket cache, | ||
| 114 | * it will use Local Security Authority (LSA) API to get the TGT. | ||
| 115 | * <P> | ||
| 116 | * <dt><b><code>ticketCache</code></b>:</dt> | ||
| 117 | * <dd>Set this to the name of the ticket | ||
| 118 | * cache that contains user's TGT. | ||
| 119 | * If this is set, <code>useTicketCache</code> | ||
| 120 | * must also be set to true; Otherwise a configuration error will | ||
| 121 | * be returned.</dd> | ||
| 122 | * <P> | ||
| 123 | * <dt><b><code>renewTGT</code></b>:</dt> | ||
| 124 | * <dd>Set this to true, if you want to renew | ||
| 125 | * the TGT. If this is set, <code>useTicketCache</code> must also be | ||
| 126 | * set to true; otherwise a configuration error will be returned.</dd> | ||
| 127 | * <p> | ||
| 128 | * <dt><b><code>doNotPrompt</code></b>:</dt> | ||
| 129 | * <dd>Set this to true if you do not want to be | ||
| 130 | * prompted for the password | ||
| 131 | * if credentials can not be obtained from the cache, the keytab, | ||
| 132 | * or through shared state.(Default is false) | ||
| 133 | * If set to true, credential must be obtained through cache, keytab, | ||
| 134 | * or shared state. Otherwise, authentication will fail.</dd> | ||
| 135 | * <P> | ||
| 136 | * <dt><b><code>useKeyTab</code></b>:</dt> | ||
| 137 | * <dd>Set this to true if you | ||
| 138 | * want the module to get the principal's key from the | ||
| 139 | * the keytab.(default value is False) | ||
| 140 | * If <code>keytab</code> | ||
| 141 | * is not set then | ||
| 142 | * the module will locate the keytab from the | ||
| 143 | * Kerberos configuration file. | ||
| 144 | * If it is not specified in the Kerberos configuration file | ||
| 145 | * then it will look for the file | ||
| 146 | * <code>{user.home}{file.separator}</code>krb5.keytab.</dd> | ||
| 147 | * <P> | ||
| 148 | * <dt><b><code>keyTab</code></b>:</dt> | ||
| 149 | * <dd>Set this to the file name of the | ||
| 150 | * keytab to get principal's secret key.</dd> | ||
| 151 | * <P> | ||
| 152 | * <dt><b><code>storeKey</code></b>:</dt> | ||
| 153 | * <dd>Set this to true to if you want the | ||
| 154 | * principal's key to be stored in the Subject's private credentials. </dd> | ||
| 155 | * <p> | ||
| 156 | * <dt><b><code>principal</code></b>:</dt> | ||
| 157 | * <dd>The name of the principal that should | ||
| 158 | * be used. The principal can be a simple username such as | ||
| 159 | * "<code>testuser</code>" or a service name such as | ||
| 160 | * "<code>host/testhost.eng.sun.com</code>". You can use the | ||
| 161 | * <code>principal</code> option to set the principal when there are | ||
| 162 | * credentials for multiple principals in the | ||
| 163 | * <code>keyTab</code> or when you want a specific ticket cache only. | ||
| 164 | * The principal can also be set using the system property | ||
| 165 | * <code>sun.security.krb5.principal</code>. In addition, if this | ||
| 166 | * system property is defined, then it will be used. If this property | ||
| 167 | * is not set, then the principal name from the configuration will be | ||
| 168 | * used.</dd> | ||
| 169 | * <P> | ||
| 170 | * <dt><b><code>isInitiator</code></b>:</dt> | ||
| 171 | * <dd>Set this to true, if initiator. Set this to false, if acceptor only. | ||
| 172 | * (Default is true). | ||
| 173 | * Note: Do not set this value to false for initiators.</dd> | ||
| 174 | * </dl></blockquote> | ||
| 175 | * | ||
| 176 | * <p> This <code>LoginModule</code> also recognizes the following additional | ||
| 177 | * <code>Configuration</code> | ||
| 178 | * options that enable you to share username and passwords across different | ||
| 179 | * authentication modules: | ||
| 180 | * <pre> | ||
| 181 | * | ||
| 182 | * useFirstPass if, true, this LoginModule retrieves the | ||
| 183 | * username and password from the module's shared state, | ||
| 184 | * using "javax.security.auth.login.name" and | ||
| 185 | * "javax.security.auth.login.password" as the respective | ||
| 186 | * keys. The retrieved values are used for authentication. | ||
| 187 | * If authentication fails, no attempt for a retry | ||
| 188 | * is made, and the failure is reported back to the | ||
| 189 | * calling application. | ||
| 190 | * | ||
| 191 | * tryFirstPass if, true, this LoginModule retrieves the | ||
| 192 | * the username and password from the module's shared | ||
| 193 | * state using "javax.security.auth.login.name" and | ||
| 194 | * "javax.security.auth.login.password" as the respective | ||
| 195 | * keys. The retrieved values are used for | ||
| 196 | * authentication. | ||
| 197 | * If authentication fails, the module uses the | ||
| 198 | * CallbackHandler to retrieve a new username | ||
| 199 | * and password, and another attempt to authenticate | ||
| 200 | * is made. If the authentication fails, | ||
| 201 | * the failure is reported back to the calling application | ||
| 202 | * | ||
| 203 | * storePass if, true, this LoginModule stores the username and | ||
| 204 | * password obtained from the CallbackHandler in the | ||
| 205 | * modules shared state, using | ||
| 206 | * "javax.security.auth.login.name" and | ||
| 207 | * "javax.security.auth.login.password" as the respective | ||
| 208 | * keys. This is not performed if existing values already | ||
| 209 | * exist for the username and password in the shared | ||
| 210 | * state, or if authentication fails. | ||
| 211 | * | ||
| 212 | * clearPass if, true, this LoginModule clears the | ||
| 213 | * username and password stored in the module's shared | ||
| 214 | * state after both phases of authentication | ||
| 215 | * (login and commit) have completed. | ||
| 216 | * </pre> | ||
| 217 | * <p>If the principal system property or key is already provided, the value of | ||
| 218 | * "javax.security.auth.login.name" in the shared state is ignored. | ||
| 219 | * <p>When multiple mechanisms to retrieve a ticket or key is provided, the | ||
| 220 | * preference order looks like this: | ||
| 221 | * <ol> | ||
| 222 | * <li>ticket cache | ||
| 223 | * <li>keytab | ||
| 224 | * <li>shared state | ||
| 225 | * <li>user prompt | ||
| 226 | * </ol> | ||
| 227 | * <p>Note that if any step fails, it will fallback to the next step. | ||
| 228 | * There's only one exception, it the shared state step fails and | ||
| 229 | * <code>useFirstPass</code>=true, no user prompt is made. | ||
| 230 | * <p>Examples of some configuration values for Krb5LoginModule in | ||
| 231 | * JAAS config file and the results are: | ||
| 232 | * <ul> | ||
| 233 | * <p> <code>doNotPrompt</code>=true; | ||
| 234 | * </ul> | ||
| 235 | * <p> This is an illegal combination since none of <code>useTicketCache</code>, | ||
| 236 | * <code>useKeyTab</code>, <code>useFirstPass</code> and <code>tryFirstPass</code> | ||
| 237 | * is set and the user can not be prompted for the password. | ||
| 238 | *<ul> | ||
| 239 | * <p> <code>ticketCache</code> = <filename>; | ||
| 240 | *</ul> | ||
| 241 | * <p> This is an illegal combination since <code>useTicketCache</code> | ||
| 242 | * is not set to true and the ticketCache is set. A configuration error | ||
| 243 | * will occur. | ||
| 244 | * <ul> | ||
| 245 | * <p> <code>renewTGT</code>=true; | ||
| 246 | *</ul> | ||
| 247 | * <p> This is an illegal combination since <code>useTicketCache</code> is | ||
| 248 | * not set to true and renewTGT is set. A configuration error will occur. | ||
| 249 | * <ul> | ||
| 250 | * <p> <code>storeKey</code>=true | ||
| 251 | * <code>useTicketCache</code> = true | ||
| 252 | * <code>doNotPrompt</code>=true;; | ||
| 253 | *</ul> | ||
| 254 | * <p> This is an illegal combination since <code>storeKey</code> is set to | ||
| 255 | * true but the key can not be obtained either by prompting the user or from | ||
| 256 | * the keytab, or from the shared state. A configuration error will occur. | ||
| 257 | * <ul> | ||
| 258 | * <p> <code>keyTab</code> = <filename> <code>doNotPrompt</code>=true ; | ||
| 259 | * </ul> | ||
| 260 | * <p>This is an illegal combination since useKeyTab is not set to true and | ||
| 261 | * the keyTab is set. A configuration error will occur. | ||
| 262 | * <ul> | ||
| 263 | * <p> <code>debug=true </code> | ||
| 264 | *</ul> | ||
| 265 | * <p> Prompt the user for the principal name and the password. | ||
| 266 | * Use the authentication exchange to get TGT from the KDC and | ||
| 267 | * populate the <code>Subject</code> with the principal and TGT. | ||
| 268 | * Output debug messages. | ||
| 269 | * <ul> | ||
| 270 | * <p> <code>useTicketCache</code> = true <code>doNotPrompt</code>=true; | ||
| 271 | *</ul> | ||
| 272 | * <p>Check the default cache for TGT and populate the <code>Subject</code> | ||
| 273 | * with the principal and TGT. If the TGT is not available, | ||
| 274 | * do not prompt the user, instead fail the authentication. | ||
| 275 | * <ul> | ||
| 276 | * <p><code>principal</code>=<name><code>useTicketCache</code> = true | ||
| 277 | * <code>doNotPrompt</code>=true; | ||
| 278 | *</ul> | ||
| 279 | * <p> Get the TGT from the default cache for the principal and populate the | ||
| 280 | * Subject's principal and private creds set. If ticket cache is | ||
| 281 | * not available or does not contain the principal's TGT | ||
| 282 | * authentication will fail. | ||
| 283 | * <ul> | ||
| 284 | * <p> <code>useTicketCache</code> = true | ||
| 285 | * <code>ticketCache</code>=<file name><code>useKeyTab</code> = true | ||
| 286 | * <code> keyTab</code>=<keytab filename> | ||
| 287 | * <code>principal</code> = <principal name> | ||
| 288 | * <code>doNotPrompt</code>=true; | ||
| 289 | *</ul> | ||
| 290 | * <p> Search the cache for the principal's TGT. If it is not available | ||
| 291 | * use the key in the keytab to perform authentication exchange with the | ||
| 292 | * KDC and acquire the TGT. | ||
| 293 | * The Subject will be populated with the principal and the TGT. | ||
| 294 | * If the key is not available or valid then authentication will fail. | ||
| 295 | * <ul> | ||
| 296 | * <p><code>useTicketCache</code> = true | ||
| 297 | * <code>ticketCache</code>=<file name> | ||
| 298 | *</ul> | ||
| 299 | * <p> The TGT will be obtained from the cache specified. | ||
| 300 | * The Kerberos principal name used will be the principal name in | ||
| 301 | * the Ticket cache. If the TGT is not available in the | ||
| 302 | * ticket cache the user will be prompted for the principal name | ||
| 303 | * and the password. The TGT will be obtained using the authentication | ||
| 304 | * exchange with the KDC. | ||
| 305 | * The Subject will be populated with the TGT. | ||
| 306 | *<ul> | ||
| 307 | * <p> <code>useKeyTab</code> = true | ||
| 308 | * <code>keyTab</code>=<keytab filename> | ||
| 309 | * <code>principal</code>= <principal name> | ||
| 310 | * <code>storeKey</code>=true; | ||
| 311 | *</ul> | ||
| 312 | * <p> The key for the principal will be retrieved from the keytab. | ||
| 313 | * If the key is not available in the keytab the user will be prompted | ||
| 314 | * for the principal's password. The Subject will be populated | ||
| 315 | * with the principal's key either from the keytab or derived from the | ||
| 316 | * password entered. | ||
| 317 | * <ul> | ||
| 318 | * <p> <code>useKeyTab</code> = true | ||
| 319 | * <code>keyTab</code>=<keytabname> | ||
| 320 | * <code>storeKey</code>=true | ||
| 321 | * <code>doNotPrompt</code>=true; | ||
| 322 | *</ul> | ||
| 323 | * <p>The user will be prompted for the service principal name. | ||
| 324 | * If the principal's | ||
| 325 | * longterm key is available in the keytab , it will be added to the | ||
| 326 | * Subject's private credentials. An authentication exchange will be | ||
| 327 | * attempted with the principal name and the key from the Keytab. | ||
| 328 | * If successful the TGT will be added to the | ||
| 329 | * Subject's private credentials set. Otherwise the authentication will | ||
| 330 | * fail. | ||
| 331 | *<ul> | ||
| 332 | * <p> | ||
| 333 | * <code>useTicketCache</code>=true | ||
| 334 | * <code>ticketCache</code>=<file name>; | ||
| 335 | * <code>useKeyTab</code> = true | ||
| 336 | * <code>keyTab</code>=<file name> <code>storeKey</code>=true | ||
| 337 | * <code>principal</code>= <principal name> | ||
| 338 | *</ul> | ||
| 339 | * <p> | ||
| 340 | * The client's TGT will be retrieved from the ticket cache and added to the | ||
| 341 | * <code>Subject</code>'s private credentials. If the TGT is not available | ||
| 342 | * in the ticket cache, or the TGT's client name does not match the principal | ||
| 343 | * name, Java will use a secret key to obtain the TGT using the authentication | ||
| 344 | * exchange and added to the Subject's private credentials. | ||
| 345 | * This secret key will be first retrieved from the keytab. If the key | ||
| 346 | * is not available, the user will be prompted for the password. In either | ||
| 347 | * case, the key derived from the password will be added to the | ||
| 348 | * Subject's private credentials set. | ||
| 349 | * <ul> | ||
| 350 | * <p><code>isInitiator</code> = false | ||
| 351 | *</ul> | ||
| 352 | * <p>Configured to act as acceptor only, credentials are not acquired | ||
| 353 | * via AS exchange. For acceptors only, set this value to false. | ||
| 354 | * For initiators, do not set this value to false. | ||
| 355 | * <ul> | ||
| 356 | * <p><code>isInitiator</code> = true | ||
| 357 | *</ul> | ||
| 358 | * <p>Configured to act as initiator, credentials are acquired | ||
| 359 | * via AS exchange. For initiators, set this value to true, or leave this | ||
| 360 | * option unset, in which case default value (true) will be used. | ||
| 361 | * | ||
| 362 | * @author Ram Marti | ||
| 363 | */ | ||
| 364 | |||
| 365 | public class Krb5LoginModule implements LoginModule { | ||
| 366 | |||
| 367 | // initial state | ||
| 368 | private Subject subject; | ||
| 369 | private CallbackHandler callbackHandler; | ||
| 370 | private Map sharedState; | ||
| 371 | private Map<String, ?> options; | ||
| 372 | |||
| 373 | // configurable option | ||
| 374 | private boolean debug = false; | ||
| 375 | private boolean storeKey = false; | ||
| 376 | private boolean doNotPrompt = false; | ||
| 377 | private boolean useTicketCache = false; | ||
| 378 | private boolean useKeyTab = false; | ||
| 379 | private String ticketCacheName = null; | ||
| 380 | private String keyTabName = null; | ||
| 381 | private String princName = null; | ||
| 382 | |||
| 383 | private boolean useFirstPass = false; | ||
| 384 | private boolean tryFirstPass = false; | ||
| 385 | private boolean storePass = false; | ||
| 386 | private boolean clearPass = false; | ||
| 387 | private boolean refreshKrb5Config = false; | ||
| 388 | private boolean renewTGT = false; | ||
| 389 | |||
| 390 | // specify if initiator. | ||
| 391 | // perform authentication exchange if initiator | ||
| 392 | private boolean isInitiator = true; | ||
| 393 | |||
| 394 | // the authentication status | ||
| 395 | private boolean succeeded = false; | ||
| 396 | private boolean commitSucceeded = false; | ||
| 397 | private String username; | ||
| 398 | |||
| 399 | // Encryption keys calculated from password. Assigned when storekey == true | ||
| 400 | // and useKeyTab == false (or true but not found) | ||
| 401 | private EncryptionKey[] encKeys = null; | ||
| 402 | |||
| 403 | KeyTab ktab = null; | ||
| 404 | |||
| 405 | private Credentials cred = null; | ||
| 406 | |||
| 407 | private PrincipalName principal = null; | ||
| 408 | private KerberosPrincipal kerbClientPrinc = null; | ||
| 409 | private KerberosTicket kerbTicket = null; | ||
| 410 | private KerberosKey[] kerbKeys = null; | ||
| 411 | private StringBuffer krb5PrincName = null; | ||
| 412 | private char[] password = null; | ||
| 413 | |||
| 414 | private static final String NAME = "javax.security.auth.login.name"; | ||
| 415 | private static final String PWD = "javax.security.auth.login.password"; | ||
| 416 | static final java.util.ResourceBundle rb = | ||
| 417 | java.util.ResourceBundle.getBundle("sun.security.util.AuthResources"); | ||
| 418 | |||
| 419 | /** | ||
| 420 | * Initialize this <code>LoginModule</code>. | ||
| 421 | * | ||
| 422 | * <p> | ||
| 423 | * @param subject the <code>Subject</code> to be authenticated. <p> | ||
| 424 | * | ||
| 425 | * @param callbackHandler a <code>CallbackHandler</code> for | ||
| 426 | * communication with the end user (prompting for | ||
| 427 | * usernames and passwords, for example). <p> | ||
| 428 | * | ||
| 429 | * @param sharedState shared <code>LoginModule</code> state. <p> | ||
| 430 | * | ||
| 431 | * @param options options specified in the login | ||
| 432 | * <code>Configuration</code> for this particular | ||
| 433 | * <code>LoginModule</code>. | ||
| 434 | */ | ||
| 435 | |||
| 436 | public void initialize(Subject subject, | ||
| 437 | CallbackHandler callbackHandler, | ||
| 438 | Map<String, ?> sharedState, | ||
| 439 | Map<String, ?> options) { | ||
| 440 | |||
| 441 | this.subject = subject; | ||
| 442 | this.callbackHandler = callbackHandler; | ||
| 443 | this.sharedState = sharedState; | ||
| 444 | this.options = options; | ||
| 445 | |||
| 446 | // initialize any configured options | ||
| 447 | |||
| 448 | debug = "true".equalsIgnoreCase((String)options.get("debug")); | ||
| 449 | storeKey = "true".equalsIgnoreCase((String)options.get("storeKey")); | ||
| 450 | doNotPrompt = "true".equalsIgnoreCase((String)options.get | ||
| 451 | ("doNotPrompt")); | ||
| 452 | useTicketCache = "true".equalsIgnoreCase((String)options.get | ||
| 453 | ("useTicketCache")); | ||
| 454 | useKeyTab = "true".equalsIgnoreCase((String)options.get("useKeyTab")); | ||
| 455 | ticketCacheName = (String)options.get("ticketCache"); | ||
| 456 | keyTabName = (String)options.get("keyTab"); | ||
| 457 | princName = (String)options.get("principal"); | ||
| 458 | refreshKrb5Config = | ||
| 459 | "true".equalsIgnoreCase((String)options.get("refreshKrb5Config")); | ||
| 460 | renewTGT = | ||
| 461 | "true".equalsIgnoreCase((String)options.get("renewTGT")); | ||
| 462 | |||
| 463 | // check isInitiator value | ||
| 464 | String isInitiatorValue = ((String)options.get("isInitiator")); | ||
| 465 | if (isInitiatorValue == null) { | ||
| 466 | // use default, if value not set | ||
| 467 | } else { | ||
| 468 | isInitiator = "true".equalsIgnoreCase(isInitiatorValue); | ||
| 469 | } | ||
| 470 | |||
| 471 | tryFirstPass = | ||
| 472 | "true".equalsIgnoreCase | ||
| 473 | ((String)options.get("tryFirstPass")); | ||
| 474 | useFirstPass = | ||
| 475 | "true".equalsIgnoreCase | ||
| 476 | ((String)options.get("useFirstPass")); | ||
| 477 | storePass = | ||
| 478 | "true".equalsIgnoreCase((String)options.get("storePass")); | ||
| 479 | clearPass = | ||
| 480 | "true".equalsIgnoreCase((String)options.get("clearPass")); | ||
| 481 | if (debug) { | ||
| 482 | System.out.print("Debug is " + debug | ||
| 483 | + " storeKey " + storeKey | ||
| 484 | + " useTicketCache " + useTicketCache | ||
| 485 | + " useKeyTab " + useKeyTab | ||
| 486 | + " doNotPrompt " + doNotPrompt | ||
| 487 | + " ticketCache is " + ticketCacheName | ||
| 488 | + " isInitiator " + isInitiator | ||
| 489 | + " KeyTab is " + keyTabName | ||
| 490 | + " refreshKrb5Config is " + refreshKrb5Config | ||
| 491 | + " principal is " + princName | ||
| 492 | + " tryFirstPass is " + tryFirstPass | ||
| 493 | + " useFirstPass is " + useFirstPass | ||
| 494 | + " storePass is " + storePass | ||
| 495 | + " clearPass is " + clearPass + "\n"); | ||
| 496 | } | ||
| 497 | } | ||
| 498 | |||
| 499 | |||
| 500 | /** | ||
| 501 | * Authenticate the user | ||
| 502 | * | ||
| 503 | * <p> | ||
| 504 | * | ||
| 505 | * @return true in all cases since this <code>LoginModule</code> | ||
| 506 | * should not be ignored. | ||
| 507 | * | ||
| 508 | * @exception FailedLoginException if the authentication fails. <p> | ||
| 509 | * | ||
| 510 | * @exception LoginException if this <code>LoginModule</code> | ||
| 511 | * is unable to perform the authentication. | ||
| 512 | */ | ||
| 513 | public boolean login() throws LoginException { | ||
| 514 | |||
| 515 | int len; | ||
| 516 | validateConfiguration(); | ||
| 517 | if (refreshKrb5Config) { | ||
| 518 | try { | ||
| 519 | if (debug) { | ||
| 520 | System.out.println("Refreshing Kerberos configuration"); | ||
| 521 | } | ||
| 522 | sun.security.krb5.Config.refresh(); | ||
| 523 | } catch (KrbException ke) { | ||
| 524 | LoginException le = new LoginException(ke.getMessage()); | ||
| 525 | le.initCause(ke); | ||
| 526 | throw le; | ||
| 527 | } | ||
| 528 | } | ||
| 529 | String principalProperty = System.getProperty | ||
| 530 | ("sun.security.krb5.principal"); | ||
| 531 | if (principalProperty != null) { | ||
| 532 | krb5PrincName = new StringBuffer(principalProperty); | ||
| 533 | } else { | ||
| 534 | if (princName != null) { | ||
| 535 | krb5PrincName = new StringBuffer(princName); | ||
| 536 | } | ||
| 537 | } | ||
| 538 | |||
| 539 | if (tryFirstPass) { | ||
| 540 | try { | ||
| 541 | attemptAuthentication(true); | ||
| 542 | if (debug) | ||
| 543 | System.out.println("\t\t[Krb5LoginModule] " + | ||
| 544 | "authentication succeeded"); | ||
| 545 | succeeded = true; | ||
| 546 | cleanState(); | ||
| 547 | return true; | ||
| 548 | } catch (LoginException le) { | ||
| 549 | // authentication failed -- try again below by prompting | ||
| 550 | cleanState(); | ||
| 551 | if (debug) { | ||
| 552 | System.out.println("\t\t[Krb5LoginModule] " + | ||
| 553 | "tryFirstPass failed with:" + | ||
| 554 | le.getMessage()); | ||
| 555 | } | ||
| 556 | } | ||
| 557 | } else if (useFirstPass) { | ||
| 558 | try { | ||
| 559 | attemptAuthentication(true); | ||
| 560 | succeeded = true; | ||
| 561 | cleanState(); | ||
| 562 | return true; | ||
| 563 | } catch (LoginException e) { | ||
| 564 | // authentication failed -- clean out state | ||
| 565 | if (debug) { | ||
| 566 | System.out.println("\t\t[Krb5LoginModule] " + | ||
| 567 | "authentication failed \n" + | ||
| 568 | e.getMessage()); | ||
| 569 | } | ||
| 570 | succeeded = false; | ||
| 571 | cleanState(); | ||
| 572 | throw e; | ||
| 573 | } | ||
| 574 | } | ||
| 575 | |||
| 576 | // attempt the authentication by getting the username and pwd | ||
| 577 | // by prompting or configuration i.e. not from shared state | ||
| 578 | |||
| 579 | try { | ||
| 580 | attemptAuthentication(false); | ||
| 581 | succeeded = true; | ||
| 582 | cleanState(); | ||
| 583 | return true; | ||
| 584 | } catch (LoginException e) { | ||
| 585 | // authentication failed -- clean out state | ||
| 586 | if (debug) { | ||
| 587 | System.out.println("\t\t[Krb5LoginModule] " + | ||
| 588 | "authentication failed \n" + | ||
| 589 | e.getMessage()); | ||
| 590 | } | ||
| 591 | succeeded = false; | ||
| 592 | cleanState(); | ||
| 593 | throw e; | ||
| 594 | } | ||
| 595 | } | ||
| 596 | /** | ||
| 597 | * process the configuration options | ||
| 598 | * Get the TGT either out of | ||
| 599 | * cache or from the KDC using the password entered | ||
| 600 | * Check the permission before getting the TGT | ||
| 601 | */ | ||
| 602 | |||
| 603 | private void attemptAuthentication(boolean getPasswdFromSharedState) | ||
| 604 | throws LoginException { | ||
| 605 | |||
| 606 | /* | ||
| 607 | * Check the creds cache to see whether | ||
| 608 | * we have TGT for this client principal | ||
| 609 | */ | ||
| 610 | if (krb5PrincName != null) { | ||
| 611 | try { | ||
| 612 | principal = new PrincipalName | ||
| 613 | (krb5PrincName.toString(), | ||
| 614 | PrincipalName.KRB_NT_PRINCIPAL); | ||
| 615 | } catch (KrbException e) { | ||
| 616 | LoginException le = new LoginException(e.getMessage()); | ||
| 617 | le.initCause(e); | ||
| 618 | throw le; | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | try { | ||
| 623 | if (useTicketCache) { | ||
| 624 | // ticketCacheName == null implies the default cache | ||
| 625 | if (debug) | ||
| 626 | System.out.println("Acquire TGT from Cache"); | ||
| 627 | cred = Credentials.acquireTGTFromCache | ||
| 628 | (principal, ticketCacheName); | ||
| 629 | |||
| 630 | if (cred != null) { | ||
| 631 | // check to renew credentials | ||
| 632 | if (!isCurrent(cred)) { | ||
| 633 | if (renewTGT) { | ||
| 634 | cred = renewCredentials(cred); | ||
| 635 | } else { | ||
| 636 | // credentials have expired | ||
| 637 | cred = null; | ||
| 638 | if (debug) | ||
| 639 | System.out.println("Credentials are" + | ||
| 640 | " no longer valid"); | ||
| 641 | } | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | if (cred != null) { | ||
| 646 | // get the principal name from the ticket cache | ||
| 647 | if (principal == null) { | ||
| 648 | principal = cred.getClient(); | ||
| 649 | } | ||
| 650 | } | ||
| 651 | if (debug) { | ||
| 652 | System.out.println("Principal is " + principal); | ||
| 653 | if (cred == null) { | ||
| 654 | System.out.println | ||
| 655 | ("null credentials from Ticket Cache"); | ||
| 656 | } | ||
| 657 | } | ||
| 658 | } | ||
| 659 | |||
| 660 | // cred = null indicates that we didn't get the creds | ||
| 661 | // from the cache or useTicketCache was false | ||
| 662 | |||
| 663 | if (cred == null) { | ||
| 664 | // We need the principal name whether we use keytab | ||
| 665 | // or AS Exchange | ||
| 666 | if (principal == null) { | ||
| 667 | promptForName(getPasswdFromSharedState); | ||
| 668 | principal = new PrincipalName | ||
| 669 | (krb5PrincName.toString(), | ||
| 670 | PrincipalName.KRB_NT_PRINCIPAL); | ||
| 671 | } | ||
| 672 | |||
| 673 | /* | ||
| 674 | * Before dynamic KeyTab support (6894072), here we check if | ||
| 675 | * the keytab contains keys for the principal. If no, keytab | ||
| 676 | * will not be used and password is prompted for. | ||
| 677 | * | ||
| 678 | * After 6894072, we normally don't check it, and expect the | ||
| 679 | * keys can be populated until a real connection is made. The | ||
| 680 | * check is still done when isInitiator == true, where the keys | ||
| 681 | * will be used right now. | ||
| 682 | * | ||
| 683 | * Probably tricky relations: | ||
| 684 | * | ||
| 685 | * useKeyTab is config flag, but when it's true but the ktab | ||
| 686 | * does not contains keys for principal, we would use password | ||
| 687 | * and keep the flag unchanged (for reuse?). In this method, | ||
| 688 | * we use (ktab != null) to check whether keytab is used. | ||
| 689 | * After this method (and when storeKey == true), we use | ||
| 690 | * (encKeys == null) to check. | ||
| 691 | */ | ||
| 692 | if (useKeyTab) { | ||
| 693 | ktab = (keyTabName == null) | ||
| 694 | ? KeyTab.getInstance() | ||
| 695 | : KeyTab.getInstance(new File(keyTabName)); | ||
| 696 | if (isInitiator) { | ||
| 697 | if (Krb5Util.keysFromJavaxKeyTab(ktab, principal).length | ||
| 698 | == 0) { | ||
| 699 | ktab = null; | ||
| 700 | if (debug) { | ||
| 701 | System.out.println | ||
| 702 | ("Key for the principal " + | ||
| 703 | principal + | ||
| 704 | " not available in " + | ||
| 705 | ((keyTabName == null) ? | ||
| 706 | "default key tab" : keyTabName)); | ||
| 707 | } | ||
| 708 | } | ||
| 709 | } | ||
| 710 | } | ||
| 711 | |||
| 712 | KrbAsReqBuilder builder; | ||
| 713 | |||
| 714 | if (ktab == null) { | ||
| 715 | promptForPass(getPasswdFromSharedState); | ||
| 716 | builder = new KrbAsReqBuilder(principal, password); | ||
| 717 | if (isInitiator) { | ||
| 718 | // XXX Even if isInitiator=false, it might be | ||
| 719 | // better to do an AS-REQ so that keys can be | ||
| 720 | // updated with PA info | ||
| 721 | cred = builder.action().getCreds(); | ||
| 722 | } | ||
| 723 | if (storeKey) { | ||
| 724 | encKeys = builder.getKeys(); | ||
| 725 | // When encKeys is empty, the login actually fails. | ||
| 726 | // For compatibility, exception is thrown in commit(). | ||
| 727 | } | ||
| 728 | } else { | ||
| 729 | builder = new KrbAsReqBuilder(principal, ktab); | ||
| 730 | if (isInitiator) { | ||
| 731 | cred = builder.action().getCreds(); | ||
| 732 | } | ||
| 733 | } | ||
| 734 | builder.destroy(); | ||
| 735 | |||
| 736 | if (debug) { | ||
| 737 | System.out.println("principal is " + principal); | ||
| 738 | HexDumpEncoder hd = new HexDumpEncoder(); | ||
| 739 | if (ktab != null) { | ||
| 740 | System.out.println("Will use keytab"); | ||
| 741 | } else if (storeKey) { | ||
| 742 | for (int i = 0; i < encKeys.length; i++) { | ||
| 743 | System.out.println("EncryptionKey: keyType=" + | ||
| 744 | encKeys[i].getEType() + | ||
| 745 | " keyBytes (hex dump)=" + | ||
| 746 | hd.encodeBuffer(encKeys[i].getBytes())); | ||
| 747 | } | ||
| 748 | } | ||
| 749 | } | ||
| 750 | |||
| 751 | // we should hava a non-null cred | ||
| 752 | if (isInitiator && (cred == null)) { | ||
| 753 | throw new LoginException | ||
| 754 | ("TGT Can not be obtained from the KDC "); | ||
| 755 | } | ||
| 756 | |||
| 757 | } | ||
| 758 | } catch (KrbException e) { | ||
| 759 | LoginException le = new LoginException(e.getMessage()); | ||
| 760 | le.initCause(e); | ||
| 761 | throw le; | ||
| 762 | } catch (IOException ioe) { | ||
| 763 | LoginException ie = new LoginException(ioe.getMessage()); | ||
| 764 | ie.initCause(ioe); | ||
| 765 | throw ie; | ||
| 766 | } | ||
| 767 | } | ||
| 768 | |||
| 769 | private void promptForName(boolean getPasswdFromSharedState) | ||
| 770 | throws LoginException { | ||
| 771 | krb5PrincName = new StringBuffer(""); | ||
| 772 | if (getPasswdFromSharedState) { | ||
| 773 | // use the name saved by the first module in the stack | ||
| 774 | username = (String)sharedState.get(NAME); | ||
| 775 | if (debug) { | ||
| 776 | System.out.println | ||
| 777 | ("username from shared state is " + username + "\n"); | ||
| 778 | } | ||
| 779 | if (username == null) { | ||
| 780 | System.out.println | ||
| 781 | ("username from shared state is null\n"); | ||
| 782 | throw new LoginException | ||
| 783 | ("Username can not be obtained from sharedstate "); | ||
| 784 | } | ||
| 785 | if (debug) { | ||
| 786 | System.out.println | ||
| 787 | ("username from shared state is " + username + "\n"); | ||
| 788 | } | ||
| 789 | if (username != null && username.length() > 0) { | ||
| 790 | krb5PrincName.insert(0, username); | ||
| 791 | return; | ||
| 792 | } | ||
| 793 | } | ||
| 794 | |||
| 795 | if (doNotPrompt) { | ||
| 796 | throw new LoginException | ||
| 797 | ("Unable to obtain Princpal Name for authentication "); | ||
| 798 | } else { | ||
| 799 | if (callbackHandler == null) | ||
| 800 | throw new LoginException("No CallbackHandler " | ||
| 801 | + "available " | ||
| 802 | + "to garner authentication " | ||
| 803 | + "information from the user"); | ||
| 804 | try { | ||
| 805 | String defUsername = System.getProperty("user.name"); | ||
| 806 | |||
| 807 | Callback[] callbacks = new Callback[1]; | ||
| 808 | MessageFormat form = new MessageFormat( | ||
| 809 | rb.getString( | ||
| 810 | "Kerberos.username.defUsername.")); | ||
| 811 | Object[] source = {defUsername}; | ||
| 812 | callbacks[0] = new NameCallback(form.format(source)); | ||
| 813 | callbackHandler.handle(callbacks); | ||
| 814 | username = ((NameCallback)callbacks[0]).getName(); | ||
| 815 | if (username == null || username.length() == 0) | ||
| 816 | username = defUsername; | ||
| 817 | krb5PrincName.insert(0, username); | ||
| 818 | |||
| 819 | } catch (java.io.IOException ioe) { | ||
| 820 | throw new LoginException(ioe.getMessage()); | ||
| 821 | } catch (UnsupportedCallbackException uce) { | ||
| 822 | throw new LoginException | ||
| 823 | (uce.getMessage() | ||
| 824 | +" not available to garner " | ||
| 825 | +" authentication information " | ||
| 826 | +" from the user"); | ||
| 827 | } | ||
| 828 | } | ||
| 829 | } | ||
| 830 | |||
| 831 | private void promptForPass(boolean getPasswdFromSharedState) | ||
| 832 | throws LoginException { | ||
| 833 | |||
| 834 | if (getPasswdFromSharedState) { | ||
| 835 | // use the password saved by the first module in the stack | ||
| 836 | password = (char[])sharedState.get(PWD); | ||
| 837 | if (password == null) { | ||
| 838 | if (debug) { | ||
| 839 | System.out.println | ||
| 840 | ("Password from shared state is null"); | ||
| 841 | } | ||
| 842 | throw new LoginException | ||
| 843 | ("Password can not be obtained from sharedstate "); | ||
| 844 | } | ||
| 845 | if (debug) { | ||
| 846 | System.out.println | ||
| 847 | ("password is " + new String(password)); | ||
| 848 | } | ||
| 849 | return; | ||
| 850 | } | ||
| 851 | if (doNotPrompt) { | ||
| 852 | throw new LoginException | ||
| 853 | ("Unable to obtain password from user\n"); | ||
| 854 | } else { | ||
| 855 | if (callbackHandler == null) | ||
| 856 | throw new LoginException("No CallbackHandler " | ||
| 857 | + "available " | ||
| 858 | + "to garner authentication " | ||
| 859 | + "information from the user"); | ||
| 860 | try { | ||
| 861 | Callback[] callbacks = new Callback[1]; | ||
| 862 | String userName = krb5PrincName.toString(); | ||
| 863 | MessageFormat form = new MessageFormat( | ||
| 864 | rb.getString( | ||
| 865 | "Kerberos.password.for.username.")); | ||
| 866 | Object[] source = {userName}; | ||
| 867 | callbacks[0] = new PasswordCallback( | ||
| 868 | form.format(source), | ||
| 869 | false); | ||
| 870 | callbackHandler.handle(callbacks); | ||
| 871 | char[] tmpPassword = ((PasswordCallback) | ||
| 872 | callbacks[0]).getPassword(); | ||
| 873 | if (tmpPassword == null) { | ||
| 874 | // treat a NULL password as an empty password | ||
| 875 | tmpPassword = new char[0]; | ||
| 876 | } | ||
| 877 | password = new char[tmpPassword.length]; | ||
| 878 | System.arraycopy(tmpPassword, 0, | ||
| 879 | password, 0, tmpPassword.length); | ||
| 880 | ((PasswordCallback)callbacks[0]).clearPassword(); | ||
| 881 | |||
| 882 | |||
| 883 | // clear tmpPassword | ||
| 884 | for (int i = 0; i < tmpPassword.length; i++) | ||
| 885 | tmpPassword[i] = ' '; | ||
| 886 | tmpPassword = null; | ||
| 887 | if (debug) { | ||
| 888 | System.out.println("\t\t[Krb5LoginModule] " + | ||
| 889 | "user entered username: " + | ||
| 890 | krb5PrincName); | ||
| 891 | System.out.println(); | ||
| 892 | } | ||
| 893 | } catch (java.io.IOException ioe) { | ||
| 894 | throw new LoginException(ioe.getMessage()); | ||
| 895 | } catch (UnsupportedCallbackException uce) { | ||
| 896 | throw new LoginException(uce.getMessage() | ||
| 897 | +" not available to garner " | ||
| 898 | +" authentication information " | ||
| 899 | + "from the user"); | ||
| 900 | } | ||
| 901 | } | ||
| 902 | } | ||
| 903 | |||
| 904 | private void validateConfiguration() throws LoginException { | ||
| 905 | if (doNotPrompt && !useTicketCache && !useKeyTab | ||
| 906 | && !tryFirstPass && !useFirstPass) | ||
| 907 | throw new LoginException | ||
| 908 | ("Configuration Error" | ||
| 909 | + " - either doNotPrompt should be " | ||
| 910 | + " false or at least one of useTicketCache, " | ||
| 911 | + " useKeyTab, tryFirstPass and useFirstPass" | ||
| 912 | + " should be true"); | ||
| 913 | if (ticketCacheName != null && !useTicketCache) | ||
| 914 | throw new LoginException | ||
| 915 | ("Configuration Error " | ||
| 916 | + " - useTicketCache should be set " | ||
| 917 | + "to true to use the ticket cache" | ||
| 918 | + ticketCacheName); | ||
| 919 | if (keyTabName != null & !useKeyTab) | ||
| 920 | throw new LoginException | ||
| 921 | ("Configuration Error - useKeyTab should be set to true " | ||
| 922 | + "to use the keytab" + keyTabName); | ||
| 923 | if (storeKey && doNotPrompt && !useKeyTab | ||
| 924 | && !tryFirstPass && !useFirstPass) | ||
| 925 | throw new LoginException | ||
| 926 | ("Configuration Error - either doNotPrompt should be set to " | ||
| 927 | + " false or at least one of tryFirstPass, useFirstPass " | ||
| 928 | + "or useKeyTab must be set to true for storeKey option"); | ||
| 929 | if (renewTGT && !useTicketCache) | ||
| 930 | throw new LoginException | ||
| 931 | ("Configuration Error" | ||
| 932 | + " - either useTicketCache should be " | ||
| 933 | + " true or renewTGT should be false"); | ||
| 934 | } | ||
| 935 | |||
| 936 | private boolean isCurrent(Credentials creds) | ||
| 937 | { | ||
| 938 | Date endTime = creds.getEndTime(); | ||
| 939 | if (endTime != null) { | ||
| 940 | return (System.currentTimeMillis() <= endTime.getTime()); | ||
| 941 | } | ||
| 942 | return true; | ||
| 943 | } | ||
| 944 | |||
| 945 | private Credentials renewCredentials(Credentials creds) | ||
| 946 | { | ||
| 947 | Credentials lcreds; | ||
| 948 | try { | ||
| 949 | if (!creds.isRenewable()) | ||
| 950 | throw new RefreshFailedException("This ticket" + | ||
| 951 | " is not renewable"); | ||
| 952 | if (System.currentTimeMillis() > cred.getRenewTill().getTime()) | ||
| 953 | throw new RefreshFailedException("This ticket is past " | ||
| 954 | + "its last renewal time."); | ||
| 955 | lcreds = creds.renew(); | ||
| 956 | if (debug) | ||
| 957 | System.out.println("Renewed Kerberos Ticket"); | ||
| 958 | } catch (Exception e) { | ||
| 959 | lcreds = null; | ||
| 960 | if (debug) | ||
| 961 | System.out.println("Ticket could not be renewed : " | ||
| 962 | + e.getMessage()); | ||
| 963 | } | ||
| 964 | return lcreds; | ||
| 965 | } | ||
| 966 | |||
| 967 | /** | ||
| 968 | * <p> This method is called if the LoginContext's | ||
| 969 | * overall authentication succeeded | ||
| 970 | * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL | ||
| 971 | * LoginModules succeeded). | ||
| 972 | * | ||
| 973 | * <p> If this LoginModule's own authentication attempt | ||
| 974 | * succeeded (checked by retrieving the private state saved by the | ||
| 975 | * <code>login</code> method), then this method associates a | ||
| 976 | * <code>Krb5Principal</code> | ||
| 977 | * with the <code>Subject</code> located in the | ||
| 978 | * <code>LoginModule</code>. It adds Kerberos Credentials to the | ||
| 979 | * the Subject's private credentials set. If this LoginModule's own | ||
| 980 | * authentication attempted failed, then this method removes | ||
| 981 | * any state that was originally saved. | ||
| 982 | * | ||
| 983 | * <p> | ||
| 984 | * | ||
| 985 | * @exception LoginException if the commit fails. | ||
| 986 | * | ||
| 987 | * @return true if this LoginModule's own login and commit | ||
| 988 | * attempts succeeded, or false otherwise. | ||
| 989 | */ | ||
| 990 | |||
| 991 | public boolean commit() throws LoginException { | ||
| 992 | |||
| 993 | /* | ||
| 994 | * Let us add the Krb5 Creds to the Subject's | ||
| 995 | * private credentials. The credentials are of type | ||
| 996 | * KerberosKey or KerberosTicket | ||
| 997 | */ | ||
| 998 | if (succeeded == false) { | ||
| 999 | return false; | ||
| 1000 | } else { | ||
| 1001 | |||
| 1002 | if (isInitiator && (cred == null)) { | ||
| 1003 | succeeded = false; | ||
| 1004 | throw new LoginException("Null Client Credential"); | ||
| 1005 | } | ||
| 1006 | |||
| 1007 | if (subject.isReadOnly()) { | ||
| 1008 | cleanKerberosCred(); | ||
| 1009 | throw new LoginException("Subject is Readonly"); | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | /* | ||
| 1013 | * Add the Principal (authenticated identity) | ||
| 1014 | * to the Subject's principal set and | ||
| 1015 | * add the credentials (TGT or Service key) to the | ||
| 1016 | * Subject's private credentials | ||
| 1017 | */ | ||
| 1018 | |||
| 1019 | Set<Object> privCredSet = subject.getPrivateCredentials(); | ||
| 1020 | Set<java.security.Principal> princSet = subject.getPrincipals(); | ||
| 1021 | kerbClientPrinc = new KerberosPrincipal(principal.getName()); | ||
| 1022 | |||
| 1023 | // create Kerberos Ticket | ||
| 1024 | if (isInitiator) { | ||
| 1025 | kerbTicket = Krb5Util.credsToTicket(cred); | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | if (storeKey && encKeys != null) { | ||
| 1029 | if (encKeys.length == 0) { | ||
| 1030 | succeeded = false; | ||
| 1031 | throw new LoginException("Null Server Key "); | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | kerbKeys = new KerberosKey[encKeys.length]; | ||
| 1035 | for (int i = 0; i < encKeys.length; i ++) { | ||
| 1036 | Integer temp = encKeys[i].getKeyVersionNumber(); | ||
| 1037 | kerbKeys[i] = new KerberosKey(kerbClientPrinc, | ||
| 1038 | encKeys[i].getBytes(), | ||
| 1039 | encKeys[i].getEType(), | ||
| 1040 | (temp == null? | ||
| 1041 | 0: temp.intValue())); | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | } | ||
| 1045 | // Let us add the kerbClientPrinc,kerbTicket and KeyTab/KerbKey (if | ||
| 1046 | // storeKey is true) | ||
| 1047 | if (!princSet.contains(kerbClientPrinc)) { | ||
| 1048 | princSet.add(kerbClientPrinc); | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | // add the TGT | ||
| 1052 | if (kerbTicket != null) { | ||
| 1053 | if (!privCredSet.contains(kerbTicket)) | ||
| 1054 | privCredSet.add(kerbTicket); | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | if (storeKey) { | ||
| 1058 | if (encKeys == null) { | ||
| 1059 | if (!privCredSet.contains(ktab)) { | ||
| 1060 | privCredSet.add(ktab); | ||
| 1061 | // Compatibility; also add keys to privCredSet | ||
| 1062 | for (KerberosKey key: ktab.getKeys(kerbClientPrinc)) { | ||
| 1063 | privCredSet.add(new Krb5Util.KeysFromKeyTab(key)); | ||
| 1064 | } | ||
| 1065 | } | ||
| 1066 | } else { | ||
| 1067 | for (int i = 0; i < kerbKeys.length; i ++) { | ||
| 1068 | if (!privCredSet.contains(kerbKeys[i])) { | ||
| 1069 | privCredSet.add(kerbKeys[i]); | ||
| 1070 | } | ||
| 1071 | encKeys[i].destroy(); | ||
| 1072 | encKeys[i] = null; | ||
| 1073 | if (debug) { | ||
| 1074 | System.out.println("Added server's key" | ||
| 1075 | + kerbKeys[i]); | ||
| 1076 | System.out.println("\t\t[Krb5LoginModule] " + | ||
| 1077 | "added Krb5Principal " + | ||
| 1078 | kerbClientPrinc.toString() | ||
| 1079 | + " to Subject"); | ||
| 1080 | } | ||
| 1081 | } | ||
| 1082 | } | ||
| 1083 | } | ||
| 1084 | } | ||
| 1085 | commitSucceeded = true; | ||
| 1086 | if (debug) | ||
| 1087 | System.out.println("Commit Succeeded \n"); | ||
| 1088 | return true; | ||
| 1089 | } | ||
| 1090 | |||
| 1091 | /** | ||
| 1092 | * <p> This method is called if the LoginContext's | ||
| 1093 | * overall authentication failed. | ||
| 1094 | * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL | ||
| 1095 | * LoginModules did not succeed). | ||
| 1096 | * | ||
| 1097 | * <p> If this LoginModule's own authentication attempt | ||
| 1098 | * succeeded (checked by retrieving the private state saved by the | ||
| 1099 | * <code>login</code> and <code>commit</code> methods), | ||
| 1100 | * then this method cleans up any state that was originally saved. | ||
| 1101 | * | ||
| 1102 | * <p> | ||
| 1103 | * | ||
| 1104 | * @exception LoginException if the abort fails. | ||
| 1105 | * | ||
| 1106 | * @return false if this LoginModule's own login and/or commit attempts | ||
| 1107 | * failed, and true otherwise. | ||
| 1108 | */ | ||
| 1109 | |||
| 1110 | public boolean abort() throws LoginException { | ||
| 1111 | if (succeeded == false) { | ||
| 1112 | return false; | ||
| 1113 | } else if (succeeded == true && commitSucceeded == false) { | ||
| 1114 | // login succeeded but overall authentication failed | ||
| 1115 | succeeded = false; | ||
| 1116 | cleanKerberosCred(); | ||
| 1117 | } else { | ||
| 1118 | // overall authentication succeeded and commit succeeded, | ||
| 1119 | // but someone else's commit failed | ||
| 1120 | logout(); | ||
| 1121 | } | ||
| 1122 | return true; | ||
| 1123 | } | ||
| 1124 | |||
| 1125 | /** | ||
| 1126 | * Logout the user. | ||
| 1127 | * | ||
| 1128 | * <p> This method removes the <code>Krb5Principal</code> | ||
| 1129 | * that was added by the <code>commit</code> method. | ||
| 1130 | * | ||
| 1131 | * <p> | ||
| 1132 | * | ||
| 1133 | * @exception LoginException if the logout fails. | ||
| 1134 | * | ||
| 1135 | * @return true in all cases since this <code>LoginModule</code> | ||
| 1136 | * should not be ignored. | ||
| 1137 | */ | ||
| 1138 | public boolean logout() throws LoginException { | ||
| 1139 | |||
| 1140 | if (debug) { | ||
| 1141 | System.out.println("\t\t[Krb5LoginModule]: " + | ||
| 1142 | "Entering logout"); | ||
| 1143 | } | ||
| 1144 | |||
| 1145 | if (subject.isReadOnly()) { | ||
| 1146 | cleanKerberosCred(); | ||
| 1147 | throw new LoginException("Subject is Readonly"); | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | subject.getPrincipals().remove(kerbClientPrinc); | ||
| 1151 | // Let us remove all Kerberos credentials stored in the Subject | ||
| 1152 | Iterator<Object> it = subject.getPrivateCredentials().iterator(); | ||
| 1153 | while (it.hasNext()) { | ||
| 1154 | Object o = it.next(); | ||
| 1155 | if (o instanceof KerberosTicket || | ||
| 1156 | o instanceof KerberosKey || | ||
| 1157 | o instanceof KeyTab) { | ||
| 1158 | it.remove(); | ||
| 1159 | } | ||
| 1160 | } | ||
| 1161 | // clean the kerberos ticket and keys | ||
| 1162 | cleanKerberosCred(); | ||
| 1163 | |||
| 1164 | succeeded = false; | ||
| 1165 | commitSucceeded = false; | ||
| 1166 | if (debug) { | ||
| 1167 | System.out.println("\t\t[Krb5LoginModule]: " + | ||
| 1168 | "logged out Subject"); | ||
| 1169 | } | ||
| 1170 | return true; | ||
| 1171 | } | ||
| 1172 | |||
| 1173 | /** | ||
| 1174 | * Clean Kerberos credentials | ||
| 1175 | */ | ||
| 1176 | private void cleanKerberosCred() throws LoginException { | ||
| 1177 | // Clean the ticket and server key | ||
| 1178 | try { | ||
| 1179 | if (kerbTicket != null) | ||
| 1180 | kerbTicket.destroy(); | ||
| 1181 | if (kerbKeys != null) { | ||
| 1182 | for (int i = 0; i < kerbKeys.length; i++) { | ||
| 1183 | kerbKeys[i].destroy(); | ||
| 1184 | } | ||
| 1185 | } | ||
| 1186 | } catch (DestroyFailedException e) { | ||
| 1187 | throw new LoginException | ||
| 1188 | ("Destroy Failed on Kerberos Private Credentials"); | ||
| 1189 | } | ||
| 1190 | kerbTicket = null; | ||
| 1191 | kerbKeys = null; | ||
| 1192 | kerbClientPrinc = null; | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | /** | ||
| 1196 | * Clean out the state | ||
| 1197 | */ | ||
| 1198 | private void cleanState() { | ||
| 1199 | |||
| 1200 | // save input as shared state only if | ||
| 1201 | // authentication succeeded | ||
| 1202 | if (succeeded) { | ||
| 1203 | if (storePass && | ||
| 1204 | !sharedState.containsKey(NAME) && | ||
| 1205 | !sharedState.containsKey(PWD)) { | ||
| 1206 | sharedState.put(NAME, username); | ||
| 1207 | sharedState.put(PWD, password); | ||
| 1208 | } | ||
| 1209 | } else { | ||
| 1210 | // remove temp results for the next try | ||
| 1211 | encKeys = null; | ||
| 1212 | ktab = null; | ||
| 1213 | principal = null; | ||
| 1214 | } | ||
| 1215 | username = null; | ||
| 1216 | password = null; | ||
| 1217 | if (krb5PrincName != null && krb5PrincName.length() != 0) | ||
| 1218 | krb5PrincName.delete(0, krb5PrincName.length()); | ||
| 1219 | krb5PrincName = null; | ||
| 1220 | if (clearPass) { | ||
| 1221 | sharedState.remove(NAME); | ||
| 1222 | sharedState.remove(PWD); | ||
| 1223 | } | ||
| 1224 | } | ||
| 1225 | } | ||