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