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

Save This Page
Home » openjdk-7 » com.sun.security » auth » module » [javadoc | source]