0001: /*
0002: * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package com.sun.security.auth.module;
0027:
0028: import java.io.*;
0029: import java.net.*;
0030: import java.text.MessageFormat;
0031: import java.util.*;
0032:
0033: import javax.security.auth.*;
0034: import javax.security.auth.kerberos.*;
0035: import javax.security.auth.callback.*;
0036: import javax.security.auth.login.*;
0037: import javax.security.auth.spi.*;
0038:
0039: import sun.security.krb5.*;
0040: import sun.security.krb5.Config;
0041: import sun.security.krb5.RealmException;
0042: import sun.security.util.AuthResources;
0043: import sun.security.jgss.krb5.Krb5Util;
0044: import sun.security.krb5.Credentials;
0045: import sun.misc.HexDumpEncoder;
0046:
0047: /**
0048: * <p> This <code>LoginModule</code> authenticates users using
0049: * Kerberos protocols.
0050: *
0051: * <p> The configuration entry for <code>Krb5LoginModule</code> has
0052: * several options that control the authentication process and
0053: * additions to the <code>Subject</code>'s private credential
0054: * set. Irrespective of these options, the <code>Subject</code>'s
0055: * principal set and private credentials set are updated only when
0056: * <code>commit</code> is called.
0057: * When <code>commit</code> is called, the <code>KerberosPrincipal</code>
0058: * is added to the <code>Subject</code>'s
0059: * principal set and <code>KerberosTicket</code> is
0060: * added to the <code>Subject</code>'s private credentials.
0061: *
0062: * <p> If the configuration entry for <code>KerberosLoginModule</code>
0063: * has the option <code>storeKey</code> set to true, then
0064: * <code>KerberosKey</code> will also be added to the
0065: * subject's private credentials. <code>KerberosKey</code>, the principal's
0066: * key will be either obtained from the keytab or
0067: * derived from user's password.
0068: *
0069: * <p> This <code>LoginModule</code> recognizes the <code>doNotPrompt</code>
0070: * option. If set to true the user will not be prompted for the password.
0071: *
0072: * <p> The user can specify the location of the ticket cache by using
0073: * the option <code>ticketCache</code> in the configuration entry.
0074: *
0075: * <p>The user can specify the keytab location by using
0076: * the option <code>keyTab</code>
0077: * in the configuration entry.
0078: *
0079: * <p> The principal name can be specified in the configuration entry
0080: * by using the option <code>principal</code>. The principal name
0081: * can either be a simple user name or a service name such as
0082: * <code>host/mission.eng.sun.com</code>. The principal can also
0083: * be set using the system property <code>sun.security.krb5.principal</code>.
0084: * This property is checked during login. If this property is not set, then
0085: * the principal name from the configuration is used. In the
0086: * case where the principal property is not set and the principal
0087: * entry also does not exist, the user is prompted for the name.
0088: *
0089: * <p> The following is a list of configuration options supported
0090: * for <code>Krb5LoginModule</code>:
0091: * <dl>
0092: * <blockquote><dt><b><code>refreshKrb5Config</code></b>:</dt>
0093: * <dd> Set this to true, if you want the configuration
0094: * to be refreshed before the <code>login</code> method is called.</dd>
0095: * <P>
0096: * <dt><b><code>useTicketCache</code></b>:</dt>
0097: * <dd>Set this to true, if you want the
0098: * TGT to be obtained
0099: * from the ticket cache. Set this option
0100: * to false if you do not want this module to use the ticket cache.
0101: * (Default is False).
0102: * This module will
0103: * search for the tickect
0104: * cache in the following locations:
0105: * For Windows 2000, it will use Local Security Authority (LSA) API
0106: * to get the TGT. On Solaris and Linux
0107: * it will look for the ticket cache in /tmp/krb5cc_<code>uid</code>
0108: * where the uid is numeric user
0109: * identifier. If the ticket cache is
0110: * not available in either of the above locations, or if we are on a
0111: * different Windows platform, it will look for the cache as
0112: * {user.home}{file.separator}krb5cc_{user.name}.
0113: * You can override the ticket cache location by using
0114: * <code>ticketCache</code>
0115: * <P>
0116: * <dt><b><code>ticketCache</code></b>:</dt>
0117: * <dd>Set this to the name of the ticket
0118: * cache that contains user's TGT.
0119: * If this is set, <code>useTicketCache</code>
0120: * must also be set to true; Otherwise a configuration error will
0121: * be returned.</dd>
0122: * <P>
0123: * <dt><b><code>renewTGT</code></b>:</dt>
0124: * <dd>Set this to true, if you want to renew
0125: * the TGT. If this is set, <code>useTicketCache</code> must also be
0126: * set to true; otherwise a configuration error will be returned.</dd>
0127: * <p>
0128: * <dt><b><code>doNotPrompt</code></b>:</dt>
0129: * <dd>Set this to true if you do not want to be
0130: * prompted for the password
0131: * if credentials can
0132: * not be obtained from the cache or keytab.(Default is false)
0133: * If set to true authentication will fail if credentials can
0134: * not be obtained from the cache or keytab.</dd>
0135: * <P>
0136: * <dt><b><code>useKeyTab</code></b>:</dt>
0137: * <dd>Set this to true if you
0138: * want the module to get the principal's key from the
0139: * the keytab.(default value is False)
0140: * If <code>keyatb</code>
0141: * is not set then
0142: * the module will locate the keytab from the
0143: * Kerberos configuration file.</dd>
0144: * If it is not specifed in the Kerberos configuration file
0145: * then it will look for the file
0146: * <code>{user.home}{file.separator}</code>krb5.keytab.</dd>
0147: * <P>
0148: * <dt><b><code>keyTab</code></b>:</dt>
0149: * <dd>Set this to the file name of the
0150: * keytab to get principal's secret key.</dd>
0151: * <P>
0152: * <dt><b><code>storeKey</code></b>:</dt>
0153: * <dd>Set this to true to if you want the
0154: * principal's key to be stored in the Subject's private credentials. </dd>
0155: * <p>
0156: * <dt><b><code>principal</code></b>:</dt>
0157: * <dd>The name of the principal that should
0158: * be used. The principal can be a simple username such as
0159: * "<code>testuser</code>" or a service name such as
0160: * "<code>host/testhost.eng.sun.com</code>". You can use the
0161: * <code>principal</code> option to set the principal when there are
0162: * credentials for multiple principals in the
0163: * <code>keyTab</code> or when you want a specific ticket cache only.
0164: * The principal can also be set using the system property
0165: * <code>sun.security.krb5.principal</code>. In addition, if this
0166: * system property is defined, then it will be used. If this property
0167: * is not set, then the principal name from the configuration will be
0168: * used.</dd>
0169: * <P>
0170: * <dt><b><code>isInitiator</code></b>:</dt>
0171: * <dd>Set this to true, if initiator. Set this to false, if acceptor only.
0172: * (Default is true).
0173: * Note: Do not set this value to false for initiators.</dd>
0174: * </dl></blockquote>
0175: *
0176: * <p> This <code>LoginModule</code> also recognizes the following additional
0177: * <code>Configuration</code>
0178: * options that enable you to share username and passwords across different
0179: * authentication modules:
0180: * <pre>
0181: *
0182: * useFirstPass if, true, this LoginModule retrieves the
0183: * username and password from the module's shared state,
0184: * using "javax.security.auth.login.name" and
0185: * "javax.security.auth.login.password" as the respective
0186: * keys. The retrieved values are used for authentication.
0187: * If authentication fails, no attempt for a retry
0188: * is made, and the failure is reported back to the
0189: * calling application.
0190: *
0191: * tryFirstPass if, true, this LoginModule retrieves the
0192: * the username and password from the module's shared
0193: * state using "javax.security.auth.login.name" and
0194: * "javax.security.auth.login.password" as the respective
0195: * keys. The retrieved values are used for
0196: * authentication.
0197: * If authentication fails, the module uses the
0198: * CallbackHandler to retrieve a new username
0199: * and password, and another attempt to authenticate
0200: * is made. If the authentication fails,
0201: * the failure is reported back to the calling application
0202: *
0203: * storePass if, true, this LoginModule stores the username and
0204: * password obtained from the CallbackHandler in the
0205: * modules shared state, using
0206: * "javax.security.auth.login.name" and
0207: * "javax.security.auth.login.password" as the respective
0208: * keys. This is not performed if existing values already
0209: * exist for the username and password in the shared
0210: * state, or if authentication fails.
0211: *
0212: * clearPass if, true, this <code>LoginModule</code> clears the
0213: * username and password stored in the module's shared
0214: * state after both phases of authentication
0215: * (login and commit) have completed.
0216: * </pre>
0217: * <p>Examples of some configuration values for Krb5LoginModule in
0218: * JAAS config file and the results are:
0219: * <ul>
0220: * <p> <code>doNotPrompt</code>=true;
0221: * </ul>
0222: * <p> This is an illegal combination since <code>useTicketCache</code>
0223: * is not set and the user can not be prompted for the password.
0224: *<ul>
0225: * <p> <code>ticketCache</code> = < filename >;
0226: *</ul>
0227: * <p> This is an illegal combination since <code>useTicketCache</code>
0228: * is not set to true and the ticketCache is set. A configuration error
0229: * will occur.
0230: * <ul>
0231: * <p> <code>renewTGT</code>=true;
0232: *</ul>
0233: * <p> This is an illegal combination since <code>useTicketCache</code> is
0234: * not set to true and renewTGT is set. A configuration error will occur.
0235: * <ul>
0236: * <p> <code>storeKey</code>=true
0237: * <code>useTicketCache</code> = true
0238: * <code>doNotPrompt</code>=true;;
0239: *</ul>
0240: * <p> This is an illegal combination since <code>storeKey</code> is set to
0241: * true but the key can not be obtained either by prompting the user or from
0242: * the keytab.A configuration error will occur.
0243: * <ul>
0244: * <p> <code>keyTab</code> = < filename > <code>doNotPrompt</code>=true ;
0245: * </ul>
0246: * <p>This is an illegal combination since useKeyTab is not set to true and
0247: * the keyTab is set. A configuration error will occur.
0248: * <ul>
0249: * <p> <code>debug=true </code>
0250: *</ul>
0251: * <p> Prompt the user for the principal name and the password.
0252: * Use the authentication exchange to get TGT from the KDC and
0253: * populate the <code>Subject</code> with the principal and TGT.
0254: * Output debug messages.
0255: * <ul>
0256: * <p> <code>useTicketCache</code> = true <code>doNotPrompt</code>=true;
0257: *</ul>
0258: * <p>Check the default cache for TGT and populate the <code>Subject</code>
0259: * with the principal and TGT. If the TGT is not available,
0260: * do not prompt the user, instead fail the authentication.
0261: * <ul>
0262: * <p><code>principal</code>=< name ><code>useTicketCache</code> = true
0263: * <code>doNotPrompt</code>=true;
0264: *</ul>
0265: * <p> Get the TGT from the default cache for the principal and populate the
0266: * Subject's principal and private creds set. If ticket cache is
0267: * not available or does not contain the principal's TGT
0268: * authentication will fail.
0269: * <ul>
0270: * <p> <code>useTicketCache</code> = true
0271: * <code>ticketCache</code>=< file name ><code>useKeyTab</code> = true
0272: * <code> keyTab</code>=< keytab filename >
0273: * <code>principal</code> = < principal name >
0274: * <code>doNotPrompt</code>=true;
0275: *</ul>
0276: * <p> Search the cache for the principal's TGT. If it is not available
0277: * use the key in the keytab to perform authentication exchange with the
0278: * KDC and acquire the TGT.
0279: * The Subject will be populated with the principal and the TGT.
0280: * If the key is not available or valid then authentication will fail.
0281: * <ul>
0282: * <p><code>useTicketCache</code> = true
0283: * <code>ticketCache</code>=< file name >
0284: *</ul>
0285: * <p> The TGT will be obtained from the cache specified.
0286: * The Kerberos principal name used will be the principal name in
0287: * the Ticket cache. If the TGT is not available in the
0288: * ticket cache the user will be prompted for the principal name
0289: * and the password. The TGT will be obtained using the authentication
0290: * exchange with the KDC.
0291: * The Subject will be populated with the TGT.
0292: *<ul>
0293: * <p> <code>useKeyTab</code> = true
0294: * <code>keyTab</code>=< keytab filename >
0295: * <code>principal</code>= < principal name >
0296: * <code>storeKey</code>=true;
0297: *</ul>
0298: * <p> The key for the principal will be retrieved from the keytab.
0299: * If the key is not available in the keytab the user will be prompted
0300: * for the principal's password. The Subject will be populated
0301: * with the principal's key either from the keytab or derived from the
0302: * password entered.
0303: * <ul>
0304: * <p> <code>useKeyTab</code> = true
0305: * <code>keyTab</code>=< keytabname >
0306: * <code>storeKey</code>=true
0307: * <code>doNotPrompt</code>=true;
0308: *</ul>
0309: * <p>The user will be prompted for the service principal name.
0310: * If the principal's
0311: * longterm key is available in the keytab , it will be added to the
0312: * Subject's private credentials. An authentication exchange will be
0313: * attempted with the principal name and the key from the Keytab.
0314: * If successful the TGT will be added to the
0315: * Subject's private credentials set. Otherwise the authentication will
0316: * fail.
0317: *<ul>
0318: * <p><code>useKeyTab</code> = true
0319: * <code>keyTab</code>=< file name > <code>storeKey</code>=true
0320: * <code>principal</code>= < principal name >
0321: * <code>useTicketCache</code>=true
0322: * <code>ticketCache</code>=< file name >;
0323: *</ul>
0324: * <p>The principal's key will be retrieved from the keytab and added
0325: * to the <code>Subject</code>'s private credentials. If the key
0326: * is not available, the
0327: * user will be prompted for the password; the key derived from the password
0328: * will be added to the Subject's private credentials set. The
0329: * client's TGT will be retrieved from the ticket cache and added to the
0330: * <code>Subject</code>'s private credentials. If the TGT is not available
0331: * in the ticket cache, it will be obtained using the authentication
0332: * exchange and added to the Subject's private credentials.
0333: * <ul>
0334: * <p><code>isInitiator</code> = false
0335: *</ul>
0336: * <p>Configured to act as acceptor only, credentials are not acquired
0337: * via AS exchange. For acceptors only, set this value to false.
0338: * For initiators, do not set this value to false.
0339: * <ul>
0340: * <p><code>isInitiator</code> = true
0341: *</ul>
0342: * <p>Configured to act as initiator, credentials are acquired
0343: * via AS exchange. For initiators, set this value to true, or leave this
0344: * option unset, in which case default value (true) will be used.
0345: *
0346: * @version 1.18, 01/11/00
0347: * @author Ram Marti
0348: */
0349:
0350: public class Krb5LoginModule implements LoginModule {
0351:
0352: // initial state
0353: private Subject subject;
0354: private CallbackHandler callbackHandler;
0355: private Map sharedState;
0356: private Map<String, ?> options;
0357:
0358: // configurable option
0359: private boolean debug = false;
0360: private boolean storeKey = false;
0361: private boolean doNotPrompt = false;
0362: private boolean useTicketCache = false;
0363: private boolean useKeyTab = false;
0364: private String ticketCacheName = null;
0365: private String keyTabName = null;
0366: private String princName = null;
0367:
0368: private boolean useFirstPass = false;
0369: private boolean tryFirstPass = false;
0370: private boolean storePass = false;
0371: private boolean clearPass = false;
0372: private boolean refreshKrb5Config = false;
0373: private boolean renewTGT = false;
0374:
0375: // specify if initiator.
0376: // perform authentication exchange if initiator
0377: private boolean isInitiator = true;
0378:
0379: // the authentication status
0380: private boolean succeeded = false;
0381: private boolean commitSucceeded = false;
0382: private String username;
0383: private EncryptionKey[] encKeys = null;
0384: private Credentials cred = null;
0385:
0386: private PrincipalName principal = null;
0387: private KerberosPrincipal kerbClientPrinc = null;
0388: private KerberosTicket kerbTicket = null;
0389: private KerberosKey[] kerbKeys = null;
0390: private StringBuffer krb5PrincName = null;
0391: private char[] password = null;
0392:
0393: private static final String NAME = "javax.security.auth.login.name";
0394: private static final String PWD = "javax.security.auth.login.password";
0395: static final java.util.ResourceBundle rb = java.util.ResourceBundle
0396: .getBundle("sun.security.util.AuthResources");
0397:
0398: /**
0399: * Initialize this <code>LoginModule</code>.
0400: *
0401: * <p>
0402: * @param subject the <code>Subject</code> to be authenticated. <p>
0403: *
0404: * @param callbackHandler a <code>CallbackHandler</code> for
0405: * communication with the end user (prompting for
0406: * usernames and passwords, for example). <p>
0407: *
0408: * @param sharedState shared <code>LoginModule</code> state. <p>
0409: *
0410: * @param options options specified in the login
0411: * <code>Configuration</code> for this particular
0412: * <code>LoginModule</code>.
0413: */
0414:
0415: public void initialize(Subject subject,
0416: CallbackHandler callbackHandler,
0417: Map<String, ?> sharedState, Map<String, ?> options) {
0418:
0419: this .subject = subject;
0420: this .callbackHandler = callbackHandler;
0421: this .sharedState = sharedState;
0422: this .options = options;
0423:
0424: // initialize any configured options
0425:
0426: debug = "true".equalsIgnoreCase((String) options.get("debug"));
0427: storeKey = "true".equalsIgnoreCase((String) options
0428: .get("storeKey"));
0429: doNotPrompt = "true".equalsIgnoreCase((String) options
0430: .get("doNotPrompt"));
0431: useTicketCache = "true".equalsIgnoreCase((String) options
0432: .get("useTicketCache"));
0433: useKeyTab = "true".equalsIgnoreCase((String) options
0434: .get("useKeyTab"));
0435: ticketCacheName = (String) options.get("ticketCache");
0436: keyTabName = (String) options.get("keyTab");
0437: princName = (String) options.get("principal");
0438: refreshKrb5Config = "true".equalsIgnoreCase((String) options
0439: .get("refreshKrb5Config"));
0440: renewTGT = "true".equalsIgnoreCase((String) options
0441: .get("renewTGT"));
0442:
0443: // check isInitiator value
0444: String isInitiatorValue = ((String) options.get("isInitiator"));
0445: if (isInitiatorValue == null) {
0446: // use default, if value not set
0447: } else {
0448: isInitiator = "true".equalsIgnoreCase(isInitiatorValue);
0449: }
0450:
0451: tryFirstPass = "true".equalsIgnoreCase((String) options
0452: .get("tryFirstPass"));
0453: useFirstPass = "true".equalsIgnoreCase((String) options
0454: .get("useFirstPass"));
0455: storePass = "true".equalsIgnoreCase((String) options
0456: .get("storePass"));
0457: clearPass = "true".equalsIgnoreCase((String) options
0458: .get("clearPass"));
0459: if (debug) {
0460: System.out.print("Debug is " + debug + " storeKey "
0461: + storeKey + " useTicketCache " + useTicketCache
0462: + " useKeyTab " + useKeyTab + " doNotPrompt "
0463: + doNotPrompt + " ticketCache is "
0464: + ticketCacheName + " isInitiator " + isInitiator
0465: + " KeyTab is " + keyTabName
0466: + " refreshKrb5Config is " + refreshKrb5Config
0467: + " principal is " + princName
0468: + " tryFirstPass is " + tryFirstPass
0469: + " useFirstPass is " + useFirstPass
0470: + " storePass is " + storePass + " clearPass is "
0471: + clearPass + "\n");
0472: }
0473: }
0474:
0475: /**
0476: * Authenticate the user
0477: *
0478: * <p>
0479: *
0480: * @return true in all cases since this <code>LoginModule</code>
0481: * should not be ignored.
0482: *
0483: * @exception FailedLoginException if the authentication fails. <p>
0484: *
0485: * @exception LoginException if this <code>LoginModule</code>
0486: * is unable to perform the authentication.
0487: */
0488: public boolean login() throws LoginException {
0489:
0490: int len;
0491: validateConfiguration();
0492: if (refreshKrb5Config) {
0493: try {
0494: if (debug) {
0495: System.out
0496: .println("Refreshing Kerberos configuration");
0497: }
0498: sun.security.krb5.Config.refresh();
0499: } catch (KrbException ke) {
0500: LoginException le = new LoginException(ke.getMessage());
0501: le.initCause(ke);
0502: throw le;
0503: }
0504: }
0505: String principalProperty = System
0506: .getProperty("sun.security.krb5.principal");
0507: if (principalProperty != null) {
0508: krb5PrincName = new StringBuffer(principalProperty);
0509: } else {
0510: if (princName != null) {
0511: krb5PrincName = new StringBuffer(princName);
0512: }
0513: }
0514:
0515: if (tryFirstPass) {
0516: try {
0517: attemptAuthentication(true);
0518: if (debug)
0519: System.out.println("\t\t[Krb5LoginModule] "
0520: + "authentication succeeded");
0521: succeeded = true;
0522: cleanState();
0523: return true;
0524: } catch (LoginException le) {
0525: // authentication failed -- try again below by prompting
0526: cleanState();
0527: if (debug) {
0528: System.out.println("\t\t[Krb5LoginModule] "
0529: + "tryFirstPass failed with:"
0530: + le.getMessage());
0531: }
0532: }
0533: } else if (useFirstPass) {
0534: try {
0535: attemptAuthentication(true);
0536: succeeded = true;
0537: cleanState();
0538: return true;
0539: } catch (LoginException e) {
0540: // authentication failed -- clean out state
0541: if (debug) {
0542: System.out.println("\t\t[Krb5LoginModule] "
0543: + "authentication failed \n"
0544: + e.getMessage());
0545: }
0546: succeeded = false;
0547: cleanState();
0548: throw e;
0549: }
0550: }
0551:
0552: // attempt the authentication by getting the username and pwd
0553: // by prompting or configuration i.e. not from shared state
0554:
0555: try {
0556: attemptAuthentication(false);
0557: succeeded = true;
0558: cleanState();
0559: return true;
0560: } catch (LoginException e) {
0561: // authentication failed -- clean out state
0562: if (debug) {
0563: System.out.println("\t\t[Krb5LoginModule] "
0564: + "authentication failed \n" + e.getMessage());
0565: }
0566: succeeded = false;
0567: cleanState();
0568: throw e;
0569: }
0570: }
0571:
0572: /**
0573: * process the configuration options
0574: * Get the TGT either out of
0575: * cache or from the KDC using the password entered
0576: * Check the permission before getting the TGT
0577: */
0578:
0579: private void attemptAuthentication(boolean getPasswdFromSharedState)
0580: throws LoginException {
0581:
0582: /*
0583: * Check the creds cache to see whether
0584: * we have TGT for this client principal
0585: */
0586: if (krb5PrincName != null) {
0587: try {
0588: principal = new PrincipalName(krb5PrincName.toString(),
0589: PrincipalName.KRB_NT_PRINCIPAL);
0590: } catch (KrbException e) {
0591: LoginException le = new LoginException(e.getMessage());
0592: le.initCause(e);
0593: throw le;
0594: }
0595: }
0596:
0597: try {
0598: if (useTicketCache) {
0599: // ticketCacheName == null implies the default cache
0600: if (debug)
0601: System.out.println("Acquire TGT from Cache");
0602: cred = Credentials.acquireTGTFromCache(principal,
0603: ticketCacheName);
0604:
0605: if (cred != null) {
0606: // check to renew credentials
0607: if (!isCurrent(cred)) {
0608: if (renewTGT) {
0609: cred = renewCredentials(cred);
0610: } else {
0611: // credentials have expired
0612: cred = null;
0613: if (debug)
0614: System.out.println("Credentials are"
0615: + " no longer valid");
0616: }
0617: }
0618: }
0619:
0620: if (cred != null) {
0621: // get the principal name from the ticket cache
0622: if (principal == null) {
0623: principal = cred.getClient();
0624: }
0625: }
0626: if (debug) {
0627: System.out.println("Principal is " + principal);
0628: if (cred == null) {
0629: System.out
0630: .println("null credentials from Ticket Cache");
0631: }
0632: }
0633: }
0634:
0635: // cred = null indicates that we didn't get the creds
0636: // from the cache or useTicketCache was false
0637:
0638: if (cred == null) {
0639: // We need the principal name whether we use keytab
0640: // or AS Exchange
0641: if (principal == null) {
0642: promptForName(getPasswdFromSharedState);
0643: principal = new PrincipalName(krb5PrincName
0644: .toString(), PrincipalName.KRB_NT_PRINCIPAL);
0645: }
0646: if (useKeyTab) {
0647: encKeys = EncryptionKey.acquireSecretKeys(
0648: principal, keyTabName);
0649:
0650: if (debug) {
0651: if (encKeys != null)
0652: System.out
0653: .println("principal's key obtained from the keytab");
0654: else
0655: System.out
0656: .println("Key for the principal "
0657: + principal
0658: + " not available in "
0659: + ((keyTabName == null) ? "default key tab"
0660: : keyTabName));
0661: }
0662:
0663: }
0664: // We can't get the key from the keytab so prompt
0665: if (encKeys == null) {
0666: promptForPass(getPasswdFromSharedState);
0667:
0668: encKeys = EncryptionKey.acquireSecretKeys(password,
0669: principal.getSalt());
0670:
0671: if (isInitiator) {
0672: if (debug)
0673: System.out
0674: .println("Acquire TGT using AS Exchange");
0675: cred = Credentials.acquireTGT(principal,
0676: encKeys, password);
0677: // update keys after pre-auth
0678: encKeys = EncryptionKey.acquireSecretKeys(
0679: password, principal.getSalt());
0680: }
0681: } else {
0682: if (isInitiator) {
0683: if (debug)
0684: System.out
0685: .println("Acquire TGT using AS Exchange");
0686: cred = Credentials.acquireTGT(principal,
0687: encKeys, password);
0688: }
0689: }
0690:
0691: // Get the TGT using AS Exchange
0692: if (debug) {
0693: System.out.println("principal is " + principal);
0694: HexDumpEncoder hd = new HexDumpEncoder();
0695: for (int i = 0; i < encKeys.length; i++) {
0696: System.out.println("EncryptionKey: keyType="
0697: + encKeys[i].getEType()
0698: + " keyBytes (hex dump)="
0699: + hd.encode(encKeys[i].getBytes()));
0700: }
0701: }
0702:
0703: // we should hava a non-null cred
0704: if (isInitiator && (cred == null)) {
0705: throw new LoginException(
0706: "TGT Can not be obtained from the KDC ");
0707: }
0708:
0709: }
0710: } catch (KrbException e) {
0711: LoginException le = new LoginException(e.getMessage());
0712: le.initCause(e);
0713: throw le;
0714: } catch (IOException ioe) {
0715: LoginException ie = new LoginException(ioe.getMessage());
0716: ie.initCause(ioe);
0717: throw ie;
0718: }
0719: }
0720:
0721: private void promptForName(boolean getPasswdFromSharedState)
0722: throws LoginException {
0723: krb5PrincName = new StringBuffer("");
0724: if (getPasswdFromSharedState) {
0725: // use the name saved by the first module in the stack
0726: username = (String) sharedState.get(NAME);
0727: if (debug) {
0728: System.out.println("username from shared state is "
0729: + username + "\n");
0730: }
0731: if (username == null) {
0732: System.out
0733: .println("username from shared state is null\n");
0734: throw new LoginException(
0735: "Username can not be obtained from sharedstate ");
0736: }
0737: if (debug) {
0738: System.out.println("username from shared state is "
0739: + username + "\n");
0740: }
0741: if (username != null && username.length() > 0) {
0742: krb5PrincName.insert(0, username);
0743: return;
0744: }
0745: }
0746:
0747: if (doNotPrompt) {
0748: throw new LoginException(
0749: "Unable to obtain Princpal Name for authentication ");
0750: } else {
0751: if (callbackHandler == null)
0752: throw new LoginException("No CallbackHandler "
0753: + "available " + "to garner authentication "
0754: + "information from the user");
0755: try {
0756: String defUsername = System.getProperty("user.name");
0757:
0758: Callback[] callbacks = new Callback[1];
0759: MessageFormat form = new MessageFormat(
0760: rb
0761: .getString("Kerberos username [[defUsername]]: "));
0762: Object[] source = { defUsername };
0763: callbacks[0] = new NameCallback(form.format(source));
0764: callbackHandler.handle(callbacks);
0765: username = ((NameCallback) callbacks[0]).getName();
0766: if (username == null || username.length() == 0)
0767: username = defUsername;
0768: krb5PrincName.insert(0, username);
0769:
0770: } catch (java.io.IOException ioe) {
0771: throw new LoginException(ioe.getMessage());
0772: } catch (UnsupportedCallbackException uce) {
0773: throw new LoginException(uce.getMessage()
0774: + " not available to garner "
0775: + " authentication information "
0776: + " from the user");
0777: }
0778: }
0779: }
0780:
0781: private void promptForPass(boolean getPasswdFromSharedState)
0782: throws LoginException {
0783:
0784: if (getPasswdFromSharedState) {
0785: // use the password saved by the first module in the stack
0786: password = (char[]) sharedState.get(PWD);
0787: if (password == null) {
0788: if (debug) {
0789: System.out
0790: .println("Password from shared state is null");
0791: }
0792: throw new LoginException(
0793: "Password can not be obtained from sharedstate ");
0794: }
0795: if (debug) {
0796: System.out.println("password is "
0797: + new String(password));
0798: }
0799: return;
0800: }
0801: if (doNotPrompt) {
0802: throw new LoginException(
0803: "Unable to obtain password from user\n");
0804: } else {
0805: if (callbackHandler == null)
0806: throw new LoginException("No CallbackHandler "
0807: + "available " + "to garner authentication "
0808: + "information from the user");
0809: try {
0810: Callback[] callbacks = new Callback[1];
0811: String userName = krb5PrincName.toString();
0812: MessageFormat form = new MessageFormat(
0813: rb
0814: .getString("Kerberos password for [username]: "));
0815: Object[] source = { userName };
0816: callbacks[0] = new PasswordCallback(
0817: form.format(source), false);
0818: callbackHandler.handle(callbacks);
0819: char[] tmpPassword = ((PasswordCallback) callbacks[0])
0820: .getPassword();
0821: if (tmpPassword == null) {
0822: // treat a NULL password as an empty password
0823: tmpPassword = new char[0];
0824: }
0825: password = new char[tmpPassword.length];
0826: System.arraycopy(tmpPassword, 0, password, 0,
0827: tmpPassword.length);
0828: ((PasswordCallback) callbacks[0]).clearPassword();
0829:
0830: // clear tmpPassword
0831: for (int i = 0; i < tmpPassword.length; i++)
0832: tmpPassword[i] = ' ';
0833: tmpPassword = null;
0834: if (debug) {
0835: System.out
0836: .println("\t\t[Krb5LoginModule] "
0837: + "user entered username: "
0838: + krb5PrincName);
0839: System.out.println();
0840: }
0841: } catch (java.io.IOException ioe) {
0842: throw new LoginException(ioe.getMessage());
0843: } catch (UnsupportedCallbackException uce) {
0844: throw new LoginException(uce.getMessage()
0845: + " not available to garner "
0846: + " authentication information "
0847: + "from the user");
0848: }
0849: }
0850: }
0851:
0852: private void validateConfiguration() throws LoginException {
0853: if (doNotPrompt && !useTicketCache && !useKeyTab)
0854: throw new LoginException("Configuration Error"
0855: + " - either doNotPrompt should be "
0856: + " false or useTicketCache/useKeyTab "
0857: + " should be true");
0858: if (ticketCacheName != null && !useTicketCache)
0859: throw new LoginException("Configuration Error "
0860: + " - useTicketCache should be set "
0861: + "to true to use the ticket cache"
0862: + ticketCacheName);
0863: if (keyTabName != null & !useKeyTab)
0864: throw new LoginException(
0865: "Configuration Error - useKeyTab should be set to true "
0866: + "to use the keytab" + keyTabName);
0867: if (storeKey && doNotPrompt && !useKeyTab)
0868: throw new LoginException(
0869: "Configuration Error - either doNotPrompt "
0870: + "should be set to false or "
0871: + "useKeyTab must be set to true for storeKey option");
0872: if (renewTGT && !useTicketCache)
0873: throw new LoginException("Configuration Error"
0874: + " - either useTicketCache should be "
0875: + " true or renewTGT should be false");
0876: }
0877:
0878: private boolean isCurrent(Credentials creds) {
0879: Date endTime = creds.getEndTime();
0880: if (endTime != null) {
0881: return (System.currentTimeMillis() <= endTime.getTime());
0882: }
0883: return true;
0884: }
0885:
0886: private Credentials renewCredentials(Credentials creds) {
0887: Credentials lcreds;
0888: try {
0889: if (!creds.isRenewable())
0890: throw new RefreshFailedException("This ticket"
0891: + " is not renewable");
0892: if (System.currentTimeMillis() > cred.getRenewTill()
0893: .getTime())
0894: throw new RefreshFailedException("This ticket is past "
0895: + "its last renewal time.");
0896: lcreds = creds.renew();
0897: if (debug)
0898: System.out.println("Renewed Kerberos Ticket");
0899: } catch (Exception e) {
0900: lcreds = null;
0901: if (debug)
0902: System.out.println("Ticket could not be renewed : "
0903: + e.getMessage());
0904: }
0905: return lcreds;
0906: }
0907:
0908: /**
0909: * <p> This method is called if the LoginContext's
0910: * overall authentication succeeded
0911: * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
0912: * LoginModules succeeded).
0913: *
0914: * <p> If this LoginModule's own authentication attempt
0915: * succeeded (checked by retrieving the private state saved by the
0916: * <code>login</code> method), then this method associates a
0917: * <code>Krb5Principal</code>
0918: * with the <code>Subject</code> located in the
0919: * <code>LoginModule</code>. It adds Kerberos Credentials to the
0920: * the Subject's private credentials set. If this LoginModule's own
0921: * authentication attempted failed, then this method removes
0922: * any state that was originally saved.
0923: *
0924: * <p>
0925: *
0926: * @exception LoginException if the commit fails.
0927: *
0928: * @return true if this LoginModule's own login and commit
0929: * attempts succeeded, or false otherwise.
0930: */
0931:
0932: public boolean commit() throws LoginException {
0933:
0934: /*
0935: * Let us add the Krb5 Creds to the Subject's
0936: * private credentials. The credentials are of type
0937: * KerberosKey or KerberosTicket
0938: */
0939: if (succeeded == false) {
0940: return false;
0941: } else {
0942:
0943: if (isInitiator && (cred == null)) {
0944: succeeded = false;
0945: throw new LoginException("Null Client Credential");
0946: }
0947:
0948: if (subject.isReadOnly()) {
0949: cleanKerberosCred();
0950: throw new LoginException("Subject is Readonly");
0951: }
0952:
0953: /*
0954: * Add the Principal (authenticated identity)
0955: * to the Subject's principal set and
0956: * add the credentials (TGT or Service key) to the
0957: * Subject's private credentials
0958: */
0959:
0960: Set<Object> privCredSet = subject.getPrivateCredentials();
0961: Set<java.security.Principal> princSet = subject
0962: .getPrincipals();
0963: kerbClientPrinc = new KerberosPrincipal(principal.getName());
0964:
0965: // create Kerberos Ticket
0966: if (isInitiator) {
0967: kerbTicket = Krb5Util.credsToTicket(cred);
0968: }
0969:
0970: if (storeKey) {
0971: if (encKeys == null || encKeys.length <= 0) {
0972: succeeded = false;
0973: throw new LoginException("Null Server Key ");
0974: }
0975:
0976: kerbKeys = new KerberosKey[encKeys.length];
0977: for (int i = 0; i < encKeys.length; i++) {
0978: Integer temp = encKeys[i].getKeyVersionNumber();
0979: kerbKeys[i] = new KerberosKey(kerbClientPrinc,
0980: encKeys[i].getBytes(), encKeys[i]
0981: .getEType(), (temp == null ? 0
0982: : temp.intValue()));
0983: }
0984:
0985: }
0986: // Let us add the kerbClientPrinc,kerbTicket and kerbKey (if
0987: // storeKey is true)
0988: if (!princSet.contains(kerbClientPrinc))
0989: princSet.add(kerbClientPrinc);
0990:
0991: // add the TGT
0992: if (kerbTicket != null) {
0993: if (!privCredSet.contains(kerbTicket))
0994: privCredSet.add(kerbTicket);
0995: }
0996:
0997: if (storeKey) {
0998: for (int i = 0; i < kerbKeys.length; i++) {
0999: if (!privCredSet.contains(kerbKeys[i])) {
1000: privCredSet.add(kerbKeys[i]);
1001: }
1002: encKeys[i].destroy();
1003: encKeys[i] = null;
1004: if (debug) {
1005: System.out.println("Added server's key"
1006: + kerbKeys[i]);
1007: System.out.println("\t\t[Krb5LoginModule] "
1008: + "added Krb5Principal "
1009: + kerbClientPrinc.toString()
1010: + " to Subject");
1011: }
1012: }
1013: }
1014: }
1015: commitSucceeded = true;
1016: if (debug)
1017: System.out.println("Commit Succeeded \n");
1018: return true;
1019: }
1020:
1021: /**
1022: * <p> This method is called if the LoginContext's
1023: * overall authentication failed.
1024: * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
1025: * LoginModules did not succeed).
1026: *
1027: * <p> If this LoginModule's own authentication attempt
1028: * succeeded (checked by retrieving the private state saved by the
1029: * <code>login</code> and <code>commit</code> methods),
1030: * then this method cleans up any state that was originally saved.
1031: *
1032: * <p>
1033: *
1034: * @exception LoginException if the abort fails.
1035: *
1036: * @return false if this LoginModule's own login and/or commit attempts
1037: * failed, and true otherwise.
1038: */
1039:
1040: public boolean abort() throws LoginException {
1041: if (succeeded == false) {
1042: return false;
1043: } else if (succeeded == true && commitSucceeded == false) {
1044: // login succeeded but overall authentication failed
1045: succeeded = false;
1046: cleanKerberosCred();
1047: } else {
1048: // overall authentication succeeded and commit succeeded,
1049: // but someone else's commit failed
1050: logout();
1051: }
1052: return true;
1053: }
1054:
1055: /**
1056: * Logout the user.
1057: *
1058: * <p> This method removes the <code>Krb5Principal</code>
1059: * that was added by the <code>commit</code> method.
1060: *
1061: * <p>
1062: *
1063: * @exception LoginException if the logout fails.
1064: *
1065: * @return true in all cases since this <code>LoginModule</code>
1066: * should not be ignored.
1067: */
1068: public boolean logout() throws LoginException {
1069:
1070: if (debug) {
1071: System.out.println("\t\t[Krb5LoginModule]: "
1072: + "Entering logout");
1073: }
1074:
1075: if (subject.isReadOnly()) {
1076: cleanKerberosCred();
1077: throw new LoginException("Subject is Readonly");
1078: }
1079:
1080: subject.getPrincipals().remove(kerbClientPrinc);
1081: // Let us remove all Kerberos credentials stored in the Subject
1082: Iterator<Object> it = subject.getPrivateCredentials()
1083: .iterator();
1084: while (it.hasNext()) {
1085: Object o = it.next();
1086: if (o instanceof KerberosTicket || o instanceof KerberosKey) {
1087: it.remove();
1088: }
1089: }
1090: // clean the kerberos ticket and keys
1091: cleanKerberosCred();
1092:
1093: succeeded = false;
1094: commitSucceeded = false;
1095: if (debug) {
1096: System.out.println("\t\t[Krb5LoginModule]: "
1097: + "logged out Subject");
1098: }
1099: return true;
1100: }
1101:
1102: /**
1103: * Clean Kerberos credentials
1104: */
1105: private void cleanKerberosCred() throws LoginException {
1106: // Clean the ticket and server key
1107: try {
1108: if (kerbTicket != null)
1109: kerbTicket.destroy();
1110: if (kerbKeys != null) {
1111: for (int i = 0; i < kerbKeys.length; i++) {
1112: kerbKeys[i].destroy();
1113: }
1114: }
1115: } catch (DestroyFailedException e) {
1116: throw new LoginException(
1117: "Destroy Failed on Kerberos Private Credentials");
1118: }
1119: kerbTicket = null;
1120: kerbKeys = null;
1121: kerbClientPrinc = null;
1122: }
1123:
1124: /**
1125: * Clean out the state
1126: */
1127: private void cleanState() {
1128:
1129: // save input as shared state only if
1130: // authentication succeeded
1131: if (succeeded) {
1132: if (storePass && !sharedState.containsKey(NAME)
1133: && !sharedState.containsKey(PWD)) {
1134: sharedState.put(NAME, username);
1135: sharedState.put(PWD, password);
1136: }
1137: }
1138: username = null;
1139: password = null;
1140: if (krb5PrincName != null && krb5PrincName.length() != 0)
1141: krb5PrincName.delete(0, krb5PrincName.length());
1142: krb5PrincName = null;
1143: if (clearPass) {
1144: sharedState.remove(NAME);
1145: sharedState.remove(PWD);
1146: }
1147: }
1148: }
|