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 @jdk.Supported 381 public class Krb5LoginModule implements LoginModule { 382 383 // initial state 384 private Subject subject; 385 private CallbackHandler callbackHandler; 386 private Map<String, Object> sharedState; 387 private Map<String, ?> options; 388 389 // configurable option 390 private boolean debug = false; 391 private boolean storeKey = false; 392 private boolean doNotPrompt = false; 393 private boolean useTicketCache = false; 394 private boolean useKeyTab = false; 395 private String ticketCacheName = null; 396 private String keyTabName = null; 397 private String princName = null; 398 399 private boolean useFirstPass = false; 400 private boolean tryFirstPass = false; 401 private boolean storePass = false; 402 private boolean clearPass = false; 403 private boolean refreshKrb5Config = false; 404 private boolean renewTGT = false; 405 406 // specify if initiator. 407 // perform authentication exchange if initiator 408 private boolean isInitiator = true; 409 410 // the authentication status 411 private boolean succeeded = false; 412 private boolean commitSucceeded = false; 413 private String username; 414 415 // Encryption keys calculated from password. Assigned when storekey == true 416 // and useKeyTab == false (or true but not found) 417 private EncryptionKey[] encKeys = null; 418 419 KeyTab ktab = null; 420 421 private Credentials cred = null; 422 423 private PrincipalName principal = null; 424 private KerberosPrincipal kerbClientPrinc = null; 425 private KerberosTicket kerbTicket = null; 426 private KerberosKey[] kerbKeys = null; 427 private StringBuffer krb5PrincName = null; 428 private boolean unboundServer = false; 429 private char[] password = null; 430 431 private static final String NAME = "javax.security.auth.login.name"; 432 private static final String PWD = "javax.security.auth.login.password"; 433 static final java.util.ResourceBundle rb = 434 java.util.ResourceBundle.getBundle("sun.security.util.AuthResources"); 435 436 /** 437 * Initialize this <code>LoginModule</code>. 438 * 439 * <p> 440 * @param subject the <code>Subject</code> to be authenticated. <p> 441 * 442 * @param callbackHandler a <code>CallbackHandler</code> for 443 * communication with the end user (prompting for 444 * usernames and passwords, for example). <p> 445 * 446 * @param sharedState shared <code>LoginModule</code> state. <p> 447 * 448 * @param options options specified in the login 449 * <code>Configuration</code> for this particular 450 * <code>LoginModule</code>. 451 */ 452 // Unchecked warning from (Map<String, Object>)sharedState is safe 453 // since javax.security.auth.login.LoginContext passes a raw HashMap. 454 // Unchecked warnings from options.get(String) are safe since we are 455 // passing known keys. 456 @SuppressWarnings("unchecked") 457 public void initialize(Subject subject, 458 CallbackHandler callbackHandler, 459 Map<String, ?> sharedState, 460 Map<String, ?> options) { 461 462 this.subject = subject; 463 this.callbackHandler = callbackHandler; 464 this.sharedState = (Map<String, Object>)sharedState; 465 this.options = options; 466 467 // initialize any configured options 468 469 debug = "true".equalsIgnoreCase((String)options.get("debug")); 470 storeKey = "true".equalsIgnoreCase((String)options.get("storeKey")); 471 doNotPrompt = "true".equalsIgnoreCase((String)options.get 472 ("doNotPrompt")); 473 useTicketCache = "true".equalsIgnoreCase((String)options.get 474 ("useTicketCache")); 475 useKeyTab = "true".equalsIgnoreCase((String)options.get("useKeyTab")); 476 ticketCacheName = (String)options.get("ticketCache"); 477 keyTabName = (String)options.get("keyTab"); 478 if (keyTabName != null) { 479 keyTabName = sun.security.krb5.internal.ktab.KeyTab.normalize( 480 keyTabName); 481 } 482 princName = (String)options.get("principal"); 483 refreshKrb5Config = 484 "true".equalsIgnoreCase((String)options.get("refreshKrb5Config")); 485 renewTGT = 486 "true".equalsIgnoreCase((String)options.get("renewTGT")); 487 488 // check isInitiator value 489 String isInitiatorValue = ((String)options.get("isInitiator")); 490 if (isInitiatorValue == null) { 491 // use default, if value not set 492 } else { 493 isInitiator = "true".equalsIgnoreCase(isInitiatorValue); 494 } 495 496 tryFirstPass = 497 "true".equalsIgnoreCase 498 ((String)options.get("tryFirstPass")); 499 useFirstPass = 500 "true".equalsIgnoreCase 501 ((String)options.get("useFirstPass")); 502 storePass = 503 "true".equalsIgnoreCase((String)options.get("storePass")); 504 clearPass = 505 "true".equalsIgnoreCase((String)options.get("clearPass")); 506 if (debug) { 507 System.out.print("Debug is " + debug 508 + " storeKey " + storeKey 509 + " useTicketCache " + useTicketCache 510 + " useKeyTab " + useKeyTab 511 + " doNotPrompt " + doNotPrompt 512 + " ticketCache is " + ticketCacheName 513 + " isInitiator " + isInitiator 514 + " KeyTab is " + keyTabName 515 + " refreshKrb5Config is " + refreshKrb5Config 516 + " principal is " + princName 517 + " tryFirstPass is " + tryFirstPass 518 + " useFirstPass is " + useFirstPass 519 + " storePass is " + storePass 520 + " clearPass is " + clearPass + "\n"); 521 } 522 } 523 524 525 /** 526 * Authenticate the user 527 * 528 * <p> 529 * 530 * @return true in all cases since this <code>LoginModule</code> 531 * should not be ignored. 532 * 533 * @exception FailedLoginException if the authentication fails. <p> 534 * 535 * @exception LoginException if this <code>LoginModule</code> 536 * is unable to perform the authentication. 537 */ 538 public boolean login() throws LoginException { 539 540 if (refreshKrb5Config) { 541 try { 542 if (debug) { 543 System.out.println("Refreshing Kerberos configuration"); 544 } 545 sun.security.krb5.Config.refresh(); 546 } catch (KrbException ke) { 547 LoginException le = new LoginException(ke.getMessage()); 548 le.initCause(ke); 549 throw le; 550 } 551 } 552 String principalProperty = System.getProperty 553 ("sun.security.krb5.principal"); 554 if (principalProperty != null) { 555 krb5PrincName = new StringBuffer(principalProperty); 556 } else { 557 if (princName != null) { 558 krb5PrincName = new StringBuffer(princName); 559 } 560 } 561 562 validateConfiguration(); 563 564 if (krb5PrincName != null && krb5PrincName.toString().equals("*")) { 565 unboundServer = true; 566 } 567 568 if (tryFirstPass) { 569 try { 570 attemptAuthentication(true); 571 if (debug) 572 System.out.println("\t\t[Krb5LoginModule] " + 573 "authentication succeeded"); 574 succeeded = true; 575 cleanState(); 576 return true; 577 } catch (LoginException le) { 578 // authentication failed -- try again below by prompting 579 cleanState(); 580 if (debug) { 581 System.out.println("\t\t[Krb5LoginModule] " + 582 "tryFirstPass failed with:" + 583 le.getMessage()); 584 } 585 } 586 } else if (useFirstPass) { 587 try { 588 attemptAuthentication(true); 589 succeeded = true; 590 cleanState(); 591 return true; 592 } catch (LoginException e) { 593 // authentication failed -- clean out state 594 if (debug) { 595 System.out.println("\t\t[Krb5LoginModule] " + 596 "authentication failed \n" + 597 e.getMessage()); 598 } 599 succeeded = false; 600 cleanState(); 601 throw e; 602 } 603 } 604 605 // attempt the authentication by getting the username and pwd 606 // by prompting or configuration i.e. not from shared state 607 608 try { 609 attemptAuthentication(false); 610 succeeded = true; 611 cleanState(); 612 return true; 613 } catch (LoginException e) { 614 // authentication failed -- clean out state 615 if (debug) { 616 System.out.println("\t\t[Krb5LoginModule] " + 617 "authentication failed \n" + 618 e.getMessage()); 619 } 620 succeeded = false; 621 cleanState(); 622 throw e; 623 } 624 } 625 /** 626 * process the configuration options 627 * Get the TGT either out of 628 * cache or from the KDC using the password entered 629 * Check the permission before getting the TGT 630 */ 631 632 private void attemptAuthentication(boolean getPasswdFromSharedState) 633 throws LoginException { 634 635 /* 636 * Check the creds cache to see whether 637 * we have TGT for this client principal 638 */ 639 if (krb5PrincName != null) { 640 try { 641 principal = new PrincipalName 642 (krb5PrincName.toString(), 643 PrincipalName.KRB_NT_PRINCIPAL); 644 } catch (KrbException e) { 645 LoginException le = new LoginException(e.getMessage()); 646 le.initCause(e); 647 throw le; 648 } 649 } 650 651 try { 652 if (useTicketCache) { 653 // ticketCacheName == null implies the default cache 654 if (debug) 655 System.out.println("Acquire TGT from Cache"); 656 cred = Credentials.acquireTGTFromCache 657 (principal, ticketCacheName); 658 659 if (cred != null) { 660 // check to renew credentials 661 if (!isCurrent(cred)) { 662 if (renewTGT) { 663 cred = renewCredentials(cred); 664 } else { 665 // credentials have expired 666 cred = null; 667 if (debug) 668 System.out.println("Credentials are" + 669 " no longer valid"); 670 } 671 } 672 } 673 674 if (cred != null) { 675 // get the principal name from the ticket cache 676 if (principal == null) { 677 principal = cred.getClient(); 678 } 679 } 680 if (debug) { 681 System.out.println("Principal is " + principal); 682 if (cred == null) { 683 System.out.println 684 ("null credentials from Ticket Cache"); 685 } 686 } 687 } 688 689 // cred = null indicates that we didn't get the creds 690 // from the cache or useTicketCache was false 691 692 if (cred == null) { 693 // We need the principal name whether we use keytab 694 // or AS Exchange 695 if (principal == null) { 696 promptForName(getPasswdFromSharedState); 697 principal = new PrincipalName 698 (krb5PrincName.toString(), 699 PrincipalName.KRB_NT_PRINCIPAL); 700 } 701 702 /* 703 * Before dynamic KeyTab support (6894072), here we check if 704 * the keytab contains keys for the principal. If no, keytab 705 * will not be used and password is prompted for. 706 * 707 * After 6894072, we normally don't check it, and expect the 708 * keys can be populated until a real connection is made. The 709 * check is still done when isInitiator == true, where the keys 710 * will be used right now. 711 * 712 * Probably tricky relations: 713 * 714 * useKeyTab is config flag, but when it's true but the ktab 715 * does not contains keys for principal, we would use password 716 * and keep the flag unchanged (for reuse?). In this method, 717 * we use (ktab != null) to check whether keytab is used. 718 * After this method (and when storeKey == true), we use 719 * (encKeys == null) to check. 720 */ 721 if (useKeyTab) { 722 if (!unboundServer) { 723 KerberosPrincipal kp = 724 new KerberosPrincipal(principal.getName()); 725 ktab = (keyTabName == null) 726 ? KeyTab.getInstance(kp) 727 : KeyTab.getInstance(kp, new File(keyTabName)); 728 } else { 729 ktab = (keyTabName == null) 730 ? KeyTab.getUnboundInstance() 731 : KeyTab.getUnboundInstance(new File(keyTabName)); 732 } 733 if (isInitiator) { 734 if (Krb5Util.keysFromJavaxKeyTab(ktab, principal).length 735 == 0) { 736 ktab = null; 737 if (debug) { 738 System.out.println 739 ("Key for the principal " + 740 principal + 741 " not available in " + 742 ((keyTabName == null) ? 743 "default key tab" : keyTabName)); 744 } 745 } 746 } 747 } 748 749 KrbAsReqBuilder builder; 750 751 if (ktab == null) { 752 promptForPass(getPasswdFromSharedState); 753 builder = new KrbAsReqBuilder(principal, password); 754 if (isInitiator) { 755 // XXX Even if isInitiator=false, it might be 756 // better to do an AS-REQ so that keys can be 757 // updated with PA info 758 cred = builder.action().getCreds(); 759 } 760 if (storeKey) { 761 encKeys = builder.getKeys(isInitiator); 762 // When encKeys is empty, the login actually fails. 763 // For compatibility, exception is thrown in commit(). 764 } 765 } else { 766 builder = new KrbAsReqBuilder(principal, ktab); 767 if (isInitiator) { 768 cred = builder.action().getCreds(); 769 } 770 } 771 builder.destroy(); 772 773 if (debug) { 774 System.out.println("principal is " + principal); 775 HexDumpEncoder hd = new HexDumpEncoder(); 776 if (ktab != null) { 777 System.out.println("Will use keytab"); 778 } else if (storeKey) { 779 for (int i = 0; i < encKeys.length; i++) { 780 System.out.println("EncryptionKey: keyType=" + 781 encKeys[i].getEType() + 782 " keyBytes (hex dump)=" + 783 hd.encodeBuffer(encKeys[i].getBytes())); 784 } 785 } 786 } 787 788 // we should hava a non-null cred 789 if (isInitiator && (cred == null)) { 790 throw new LoginException 791 ("TGT Can not be obtained from the KDC "); 792 } 793 794 } 795 } catch (KrbException e) { 796 LoginException le = new LoginException(e.getMessage()); 797 le.initCause(e); 798 throw le; 799 } catch (IOException ioe) { 800 LoginException ie = new LoginException(ioe.getMessage()); 801 ie.initCause(ioe); 802 throw ie; 803 } 804 } 805 806 private void promptForName(boolean getPasswdFromSharedState) 807 throws LoginException { 808 krb5PrincName = new StringBuffer(""); 809 if (getPasswdFromSharedState) { 810 // use the name saved by the first module in the stack 811 username = (String)sharedState.get(NAME); 812 if (debug) { 813 System.out.println 814 ("username from shared state is " + username + "\n"); 815 } 816 if (username == null) { 817 System.out.println 818 ("username from shared state is null\n"); 819 throw new LoginException 820 ("Username can not be obtained from sharedstate "); 821 } 822 if (debug) { 823 System.out.println 824 ("username from shared state is " + username + "\n"); 825 } 826 if (username != null && username.length() > 0) { 827 krb5PrincName.insert(0, username); 828 return; 829 } 830 } 831 832 if (doNotPrompt) { 833 throw new LoginException 834 ("Unable to obtain Principal Name for authentication "); 835 } else { 836 if (callbackHandler == null) 837 throw new LoginException("No CallbackHandler " 838 + "available " 839 + "to garner authentication " 840 + "information from the user"); 841 try { 842 String defUsername = System.getProperty("user.name"); 843 844 Callback[] callbacks = new Callback[1]; 845 MessageFormat form = new MessageFormat( 846 rb.getString( 847 "Kerberos.username.defUsername.")); 848 Object[] source = {defUsername}; 849 callbacks[0] = new NameCallback(form.format(source)); 850 callbackHandler.handle(callbacks); 851 username = ((NameCallback)callbacks[0]).getName(); 852 if (username == null || username.length() == 0) 853 username = defUsername; 854 krb5PrincName.insert(0, username); 855 856 } catch (java.io.IOException ioe) { 857 throw new LoginException(ioe.getMessage()); 858 } catch (UnsupportedCallbackException uce) { 859 throw new LoginException 860 (uce.getMessage() 861 +" not available to garner " 862 +" authentication information " 863 +" from the user"); 864 } 865 } 866 } 867 868 private void promptForPass(boolean getPasswdFromSharedState) 869 throws LoginException { 870 871 if (getPasswdFromSharedState) { 872 // use the password saved by the first module in the stack 873 password = (char[])sharedState.get(PWD); 874 if (password == null) { 875 if (debug) { 876 System.out.println 877 ("Password from shared state is null"); 878 } 879 throw new LoginException 880 ("Password can not be obtained from sharedstate "); 881 } 882 if (debug) { 883 System.out.println 884 ("password is " + new String(password)); 885 } 886 return; 887 } 888 if (doNotPrompt) { 889 throw new LoginException 890 ("Unable to obtain password from user\n"); 891 } else { 892 if (callbackHandler == null) 893 throw new LoginException("No CallbackHandler " 894 + "available " 895 + "to garner authentication " 896 + "information from the user"); 897 try { 898 Callback[] callbacks = new Callback[1]; 899 String userName = krb5PrincName.toString(); 900 MessageFormat form = new MessageFormat( 901 rb.getString( 902 "Kerberos.password.for.username.")); 903 Object[] source = {userName}; 904 callbacks[0] = new PasswordCallback( 905 form.format(source), 906 false); 907 callbackHandler.handle(callbacks); 908 char[] tmpPassword = ((PasswordCallback) 909 callbacks[0]).getPassword(); 910 if (tmpPassword == null) { 911 // treat a NULL password as an empty password 912 tmpPassword = new char[0]; 913 } 914 password = new char[tmpPassword.length]; 915 System.arraycopy(tmpPassword, 0, 916 password, 0, tmpPassword.length); 917 ((PasswordCallback)callbacks[0]).clearPassword(); 918 919 920 // clear tmpPassword 921 for (int i = 0; i < tmpPassword.length; i++) 922 tmpPassword[i] = ' '; 923 tmpPassword = null; 924 if (debug) { 925 System.out.println("\t\t[Krb5LoginModule] " + 926 "user entered username: " + 927 krb5PrincName); 928 System.out.println(); 929 } 930 } catch (java.io.IOException ioe) { 931 throw new LoginException(ioe.getMessage()); 932 } catch (UnsupportedCallbackException uce) { 933 throw new LoginException(uce.getMessage() 934 +" not available to garner " 935 +" authentication information " 936 + "from the user"); 937 } 938 } 939 } 940 941 private void validateConfiguration() throws LoginException { 942 if (doNotPrompt && !useTicketCache && !useKeyTab 943 && !tryFirstPass && !useFirstPass) 944 throw new LoginException 945 ("Configuration Error" 946 + " - either doNotPrompt should be " 947 + " false or at least one of useTicketCache, " 948 + " useKeyTab, tryFirstPass and useFirstPass" 949 + " should be true"); 950 if (ticketCacheName != null && !useTicketCache) 951 throw new LoginException 952 ("Configuration Error " 953 + " - useTicketCache should be set " 954 + "to true to use the ticket cache" 955 + ticketCacheName); 956 if (keyTabName != null & !useKeyTab) 957 throw new LoginException 958 ("Configuration Error - useKeyTab should be set to true " 959 + "to use the keytab" + keyTabName); 960 if (storeKey && doNotPrompt && !useKeyTab 961 && !tryFirstPass && !useFirstPass) 962 throw new LoginException 963 ("Configuration Error - either doNotPrompt should be set to " 964 + " false or at least one of tryFirstPass, useFirstPass " 965 + "or useKeyTab must be set to true for storeKey option"); 966 if (renewTGT && !useTicketCache) 967 throw new LoginException 968 ("Configuration Error" 969 + " - either useTicketCache should be " 970 + " true or renewTGT should be false"); 971 if (krb5PrincName != null && krb5PrincName.toString().equals("*")) { 972 if (isInitiator) { 973 throw new LoginException 974 ("Configuration Error" 975 + " - principal cannot be * when isInitiator is true"); 976 } 977 } 978 } 979 980 private boolean isCurrent(Credentials creds) 981 { 982 Date endTime = creds.getEndTime(); 983 if (endTime != null) { 984 return (System.currentTimeMillis() <= endTime.getTime()); 985 } 986 return true; 987 } 988 989 private Credentials renewCredentials(Credentials creds) 990 { 991 Credentials lcreds; 992 try { 993 if (!creds.isRenewable()) 994 throw new RefreshFailedException("This ticket" + 995 " is not renewable"); 996 if (System.currentTimeMillis() > cred.getRenewTill().getTime()) 997 throw new RefreshFailedException("This ticket is past " 998 + "its last renewal time."); 999 lcreds = creds.renew(); 1000 if (debug) 1001 System.out.println("Renewed Kerberos Ticket"); 1002 } catch (Exception e) { 1003 lcreds = null; 1004 if (debug) 1005 System.out.println("Ticket could not be renewed : " 1006 + e.getMessage()); 1007 } 1008 return lcreds; 1009 } 1010 1011 /** 1012 * <p> This method is called if the LoginContext's 1013 * overall authentication succeeded 1014 * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL 1015 * LoginModules succeeded). 1016 * 1017 * <p> If this LoginModule's own authentication attempt 1018 * succeeded (checked by retrieving the private state saved by the 1019 * <code>login</code> method), then this method associates a 1020 * <code>Krb5Principal</code> 1021 * with the <code>Subject</code> located in the 1022 * <code>LoginModule</code>. It adds Kerberos Credentials to the 1023 * the Subject's private credentials set. If this LoginModule's own 1024 * authentication attempted failed, then this method removes 1025 * any state that was originally saved. 1026 * 1027 * <p> 1028 * 1029 * @exception LoginException if the commit fails. 1030 * 1031 * @return true if this LoginModule's own login and commit 1032 * attempts succeeded, or false otherwise. 1033 */ 1034 1035 public boolean commit() throws LoginException { 1036 1037 /* 1038 * Let us add the Krb5 Creds to the Subject's 1039 * private credentials. The credentials are of type 1040 * KerberosKey or KerberosTicket 1041 */ 1042 if (succeeded == false) { 1043 return false; 1044 } else { 1045 1046 if (isInitiator && (cred == null)) { 1047 succeeded = false; 1048 throw new LoginException("Null Client Credential"); 1049 } 1050 1051 if (subject.isReadOnly()) { 1052 cleanKerberosCred(); 1053 throw new LoginException("Subject is Readonly"); 1054 } 1055 1056 /* 1057 * Add the Principal (authenticated identity) 1058 * to the Subject's principal set and 1059 * add the credentials (TGT or Service key) to the 1060 * Subject's private credentials 1061 */ 1062 1063 Set<Object> privCredSet = subject.getPrivateCredentials(); 1064 Set<java.security.Principal> princSet = subject.getPrincipals(); 1065 kerbClientPrinc = new KerberosPrincipal(principal.getName()); 1066 1067 // create Kerberos Ticket 1068 if (isInitiator) { 1069 kerbTicket = Krb5Util.credsToTicket(cred); 1070 } 1071 1072 if (storeKey && encKeys != null) { 1073 if (encKeys.length == 0) { 1074 succeeded = false; 1075 throw new LoginException("Null Server Key "); 1076 } 1077 1078 kerbKeys = new KerberosKey[encKeys.length]; 1079 for (int i = 0; i < encKeys.length; i ++) { 1080 Integer temp = encKeys[i].getKeyVersionNumber(); 1081 kerbKeys[i] = new KerberosKey(kerbClientPrinc, 1082 encKeys[i].getBytes(), 1083 encKeys[i].getEType(), 1084 (temp == null? 1085 0: temp.intValue())); 1086 } 1087 1088 } 1089 // Let us add the kerbClientPrinc,kerbTicket and KeyTab/KerbKey (if 1090 // storeKey is true) 1091 1092 // We won't add "*" as a KerberosPrincipal 1093 if (!unboundServer && 1094 !princSet.contains(kerbClientPrinc)) { 1095 princSet.add(kerbClientPrinc); 1096 } 1097 1098 // add the TGT 1099 if (kerbTicket != null) { 1100 if (!privCredSet.contains(kerbTicket)) 1101 privCredSet.add(kerbTicket); 1102 } 1103 1104 if (storeKey) { 1105 if (encKeys == null) { 1106 if (ktab != null) { 1107 if (!privCredSet.contains(ktab)) { 1108 privCredSet.add(ktab); 1109 } 1110 } else { 1111 succeeded = false; 1112 throw new LoginException("No key to store"); 1113 } 1114 } else { 1115 for (int i = 0; i < kerbKeys.length; i ++) { 1116 if (!privCredSet.contains(kerbKeys[i])) { 1117 privCredSet.add(kerbKeys[i]); 1118 } 1119 encKeys[i].destroy(); 1120 encKeys[i] = null; 1121 if (debug) { 1122 System.out.println("Added server's key" 1123 + kerbKeys[i]); 1124 System.out.println("\t\t[Krb5LoginModule] " + 1125 "added Krb5Principal " + 1126 kerbClientPrinc.toString() 1127 + " to Subject"); 1128 } 1129 } 1130 } 1131 } 1132 } 1133 commitSucceeded = true; 1134 if (debug) 1135 System.out.println("Commit Succeeded \n"); 1136 return true; 1137 } 1138 1139 /** 1140 * <p> This method is called if the LoginContext's 1141 * overall authentication failed. 1142 * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL 1143 * LoginModules did not succeed). 1144 * 1145 * <p> If this LoginModule's own authentication attempt 1146 * succeeded (checked by retrieving the private state saved by the 1147 * <code>login</code> and <code>commit</code> methods), 1148 * then this method cleans up any state that was originally saved. 1149 * 1150 * <p> 1151 * 1152 * @exception LoginException if the abort fails. 1153 * 1154 * @return false if this LoginModule's own login and/or commit attempts 1155 * failed, and true otherwise. 1156 */ 1157 1158 public boolean abort() throws LoginException { 1159 if (succeeded == false) { 1160 return false; 1161 } else if (succeeded == true && commitSucceeded == false) { 1162 // login succeeded but overall authentication failed 1163 succeeded = false; 1164 cleanKerberosCred(); 1165 } else { 1166 // overall authentication succeeded and commit succeeded, 1167 // but someone else's commit failed 1168 logout(); 1169 } 1170 return true; 1171 } 1172 1173 /** 1174 * Logout the user. 1175 * 1176 * <p> This method removes the <code>Krb5Principal</code> 1177 * that was added by the <code>commit</code> method. 1178 * 1179 * <p> 1180 * 1181 * @exception LoginException if the logout fails. 1182 * 1183 * @return true in all cases since this <code>LoginModule</code> 1184 * should not be ignored. 1185 */ 1186 public boolean logout() throws LoginException { 1187 1188 if (debug) { 1189 System.out.println("\t\t[Krb5LoginModule]: " + 1190 "Entering logout"); 1191 } 1192 1193 if (subject.isReadOnly()) { 1194 cleanKerberosCred(); 1195 throw new LoginException("Subject is Readonly"); 1196 } 1197 1198 subject.getPrincipals().remove(kerbClientPrinc); 1199 // Let us remove all Kerberos credentials stored in the Subject 1200 Iterator<Object> it = subject.getPrivateCredentials().iterator(); 1201 while (it.hasNext()) { 1202 Object o = it.next(); 1203 if (o instanceof KerberosTicket || 1204 o instanceof KerberosKey || 1205 o instanceof KeyTab) { 1206 it.remove(); 1207 } 1208 } 1209 // clean the kerberos ticket and keys 1210 cleanKerberosCred(); 1211 1212 succeeded = false; 1213 commitSucceeded = false; 1214 if (debug) { 1215 System.out.println("\t\t[Krb5LoginModule]: " + 1216 "logged out Subject"); 1217 } 1218 return true; 1219 } 1220 1221 /** 1222 * Clean Kerberos credentials 1223 */ 1224 private void cleanKerberosCred() throws LoginException { 1225 // Clean the ticket and server key 1226 try { 1227 if (kerbTicket != null) 1228 kerbTicket.destroy(); 1229 if (kerbKeys != null) { 1230 for (int i = 0; i < kerbKeys.length; i++) { 1231 kerbKeys[i].destroy(); 1232 } 1233 } 1234 } catch (DestroyFailedException e) { 1235 throw new LoginException 1236 ("Destroy Failed on Kerberos Private Credentials"); 1237 } 1238 kerbTicket = null; 1239 kerbKeys = null; 1240 kerbClientPrinc = null; 1241 } 1242 1243 /** 1244 * Clean out the state 1245 */ 1246 private void cleanState() { 1247 1248 // save input as shared state only if 1249 // authentication succeeded 1250 if (succeeded) { 1251 if (storePass && 1252 !sharedState.containsKey(NAME) && 1253 !sharedState.containsKey(PWD)) { 1254 sharedState.put(NAME, username); 1255 sharedState.put(PWD, password); 1256 } 1257 } else { 1258 // remove temp results for the next try 1259 encKeys = null; 1260 ktab = null; 1261 principal = null; 1262 } 1263 username = null; 1264 password = null; 1265 if (krb5PrincName != null && krb5PrincName.length() != 0) 1266 krb5PrincName.delete(0, krb5PrincName.length()); 1267 krb5PrincName = null; 1268 if (clearPass) { 1269 sharedState.remove(NAME); 1270 sharedState.remove(PWD); 1271 } 1272 } 1273 }