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