1. /*
  2. * @(#)Krb5LoginModule.java 1.21 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.security.auth.module;
  8. import java.io.*;
  9. import java.net.*;
  10. import java.text.MessageFormat;
  11. import java.util.*;
  12. import javax.security.auth.*;
  13. import javax.security.auth.kerberos.*;
  14. import javax.security.auth.callback.*;
  15. import javax.security.auth.login.*;
  16. import javax.security.auth.spi.*;
  17. import sun.security.krb5.*;
  18. import sun.security.krb5.Config;
  19. import sun.security.krb5.RealmException;
  20. import sun.security.util.AuthResources;
  21. /**
  22. * <p> This <code>LoginModule</code> authenticates users using
  23. * Kerberos protocols.
  24. * <p> Configuration entry for <code>Krb5LoginModule</code> has
  25. * several options that control the authentication process and
  26. * additions to the <code>Subject</code>'s private credential
  27. * set.
  28. * Irrespective of the options, only when <code>commit</code>
  29. * is called the Subject's principal set and private credentials
  30. * set are updated.
  31. * When <code>commit</code> is called the <code>KerberosPrincipal</code>
  32. * is added to the <code>Subject</code>'s
  33. * principal set and <code>KerberosTicket</code> will be
  34. * added to the <code>Subject</code>'s private credentials.
  35. *
  36. * <p> If the configuration entry for
  37. * KerberosLoginModule has the option <code>storeKey</code> set to true ,
  38. * then
  39. * <code>KerberosKey</code> will also be added to the
  40. * subject's private credentials. <code>KerberosKey</code>, the principal's
  41. * key will be obtained either from the keytab or
  42. * derived from user's password
  43. *
  44. * <p> This LoginModule recogonizes the <code>doNotPrompt</code> option.
  45. * If set to true the user will not be prompted for the password.
  46. *
  47. * <p> The user can specify the location of the ticket cache by using
  48. * the option <code>ticketCache</code> in the configuration entry.
  49. *
  50. * <p>The user can specify the keytab location by using
  51. * the option <code>keyTab</code>
  52. * in the configuraion entry.
  53. *
  54. * <p> The principal name can be specified in the configuration entry
  55. * by
  56. * using the option <code>principal</code> The principal name
  57. * can either be a simple
  58. * user name or a service name such as
  59. * <code>host/mission.eng.sun.com</code>
  60. *
  61. * <p> The following are the list of configuration options supported
  62. * for <code>Krb5LoginModule</code>
  63. *<p><code>useTicketCache</code>: Set this to true, if you want the
  64. * TGT to be obtained
  65. * from the ticket cache. Set this option
  66. * to false if you do not want this module to use the ticket cache.
  67. * (Default is False).
  68. * This module will
  69. * search for the tickect
  70. * cache in the following locations:
  71. * For Windows 2000, it will use Local Security Authority (LSA) API
  72. * to get the TGT. On Solaris and Linux
  73. * it will look for the ticket cache in /tmp/krb5cc_<code>uid</code>
  74. * where the uid is numeric user
  75. * identifier. If the ticket cache is
  76. * not available in either of the above locations, or if we are on a
  77. * different WIndows platform, it will look for the cache as
  78. * {user.home}{file.separator}krb5cc_{user.name}.
  79. * You can override the ticket cache location by using
  80. * <code>ticketCache</code>
  81. *
  82. * <p><code>ticketCache</code>: Set this to the name of the ticket
  83. * cache that contains user's TGT.
  84. * If this is set, <code>useTicketcache</code>
  85. * must also be set to true; Otherwise a configuration error will
  86. * be returned.
  87. *
  88. * <p> <code>doNotPrompt</code>: Set this to true if you do not want to be
  89. * prompted for the password
  90. * if credentials can
  91. * not be obtained from the cache or keytab.(Default is false)
  92. * If set to true authentication will fail if credentials can
  93. * not be obtained from the cache or keytab.
  94. *
  95. *<p><code>useKeyTab</code>:Set this to true if you
  96. * want the module to get the principal's key from the
  97. * the keytab.(default value is False)
  98. * If <code>keyatb</code>
  99. * is not set then
  100. * the module will locate the keytab from the
  101. * Kerberos configuration file.
  102. * If it is not specifed in the Kerberos configuration file
  103. * then it will look for the file
  104. * <code>{user.home}{file.separator}</code>krb5.keytab.
  105. *
  106. * <p><code>keyTab</code>: Set this to the file name of the
  107. * keytab to get principal's secret key.
  108. *
  109. * <p> <code>storeKey</code>: Set this to True to if you want the
  110. * principal's key to be stored in the Subject's private credentials.
  111. *
  112. * <p> <code>principal</code>: The name of the principal that should
  113. * be used. It could be
  114. * simple username such as <code>"testuser"</code> or a service name
  115. * such as
  116. * <code>"host/testhost.eng.sun.com" </code>. You can use
  117. * <code>principal</code> option to set the principal when there are
  118. * credentials for multiple principals in the
  119. * <code>keyTab</code> or when you want a specific ticket cache only.
  120. *
  121. * <p> This LoginModule also recognizes the following additional
  122. * <code>Configuration</code>
  123. * options that enable you to share username and passwords across different
  124. * authentication modules:
  125. * <pre>
  126. *
  127. * useFirstPass if, true, this LoginModule retrieves the
  128. * username and password from the module's shared state,
  129. * using "javax.security.auth.login.name" and
  130. * "javax.security.auth.login.password" as the respective
  131. * keys. The retrieved values are used for authentication.
  132. * If authentication fails, no attempt for a retry
  133. * is made, and the failure is reported back to the
  134. * calling application.
  135. *
  136. * tryFirstPass if, true, this LoginModule retrieves the
  137. * the username and password from the module's shared
  138. * state using "javax.security.auth.login.name" and
  139. * "javax.security.auth.login.password" as the respective
  140. * keys. The retrieved values are used for
  141. * authentication.
  142. * If authentication fails, the module uses the
  143. * CallbackHandler to retrieve a new username
  144. * and password, and another attempt to authenticate
  145. * is made. If the authentication fails,
  146. * the failure is reported back to the calling application
  147. *
  148. * storePass if, true, this LoginModule stores the username and
  149. * password obtained from the CallbackHandler in the
  150. * modules shared state, using
  151. * "javax.security.auth.login.name" and
  152. * "javax.security.auth.login.password" as the respective
  153. * keys. This is not performed if existing values already
  154. * exist for the username and password in the shared
  155. * state, or if authentication fails.
  156. *
  157. * clearPass if, true, this <code>LoginModule</code> clears the
  158. * username and password stored in the module's shared
  159. * state after both phases of authentication
  160. * (login and commit) have completed.
  161. * </pre>
  162. * <p>Examples of some configuration values for Krb5LoginModule in
  163. * JAAS config file and the results are:
  164. * <ul>
  165. * <p> <code>doNotPrompt</code>=true;
  166. * </ul>
  167. * <p> This is an illegal combination since <code>useTicketCache</code>
  168. * is not set and the user can not be prompted for the password.
  169. *<ul>
  170. * <p> <code>ticketCache</code> = < filename >
  171. *</ul>
  172. * <p> This is an illegal combination since useTicketCache is not set to
  173. * true and the ticketCache is set. A configuratin error will occur.
  174. * <ul>
  175. * <p> <code>storeKey</code>=true
  176. * <code>useTicketCache</code> = true
  177. * <code>doNotPrompt</code>=true;;
  178. *</ul>
  179. * <p> This is an illegal combination since <code>storeKey</code> is set to
  180. * true but the key can not be obtained either by prompting the user or from
  181. * the keytab.A configuratin error will occur.
  182. * <ul>
  183. * <p> <code>keyTab</code> = < filename > <code>doNotPrompt</code>=true ;
  184. * </ul>
  185. * <p>This is an illegal combination since useKeyTab is not set to true and
  186. * the keyTab is set. A configuration error will occur.
  187. * <ul>
  188. * <p> <code>debug=true </code>
  189. *</ul>
  190. * <p> Prompt the user for the principal name and the password.
  191. * Use the authentication exchange to get TGT from the KDC and
  192. * populate the <code>Subject</code> with the principal and TGT.
  193. * Output debug messages.
  194. * <ul>
  195. * <p> <code>useTicketCache</code> = true <code>doNotPrompt</code>=true;
  196. *</ul>
  197. * <p>Check the default cache for TGT and populate the <code>Subject</code>
  198. * with the principal and TGT. If the TGT is not available,
  199. * do not prompt the user, instead fail the authentication.
  200. * <ul>
  201. * <p><code>principal</code>=< name ><code>useTicketCache</code> = true
  202. * <code>doNotPrompt</code>=true;
  203. *</ul>
  204. * <p> Get the TGT from the default cache for the principal and populate the
  205. * Subject's principal and private creds set. If ticket cache is
  206. * not available or does not contain the principal's TGT
  207. * authentication will fail.
  208. * <ul>
  209. * <p> <code>useTicketCache</code> = true
  210. * <code>ticketCache</code>=< file name ><code>useKeyTab</code> = true
  211. * <code> keyTab</code>=< keytab filename >
  212. * <code>principal</code> = < principal name >
  213. * <code>doNotPrompt</code>=true;
  214. *</ul>
  215. * <p> Search the cache for the principal's TGT. If it is not available
  216. * use the key in the keytab to perform authentication exchange with the
  217. * KDC and acquire the TGT.
  218. * The Subject will be populated with the principal and the TGT.
  219. * If the key is not available or valid then authentication will fail.
  220. * <ul>
  221. * <p><code>useTicketCache</code> = true
  222. * <code>ticketCache</code>=< file name >
  223. *</ul>
  224. * <p> The TGT will be obtained from the cache specified.
  225. * The Kerberos principal name used will be the principal name in
  226. * the Ticket cache. If the TGT is not available in the
  227. * ticket cache the user will be prompted for the principal name
  228. * and the password. The TGT will be obtained using the authentication
  229. * exchange with the KDC.
  230. * The Subject will be populated with the TGT.
  231. *<ul>
  232. * <p> <code>useKeyTab</code> = true
  233. * <code>keyTab</code>=< keytab filename >
  234. * <code>principal</code>= < principal name >
  235. * <code>storeKey</code>=true;
  236. *</ul>
  237. * <p> The key for the principal will be retrieved from the keytab.
  238. * If the key is not available in the keytab the user will be prompted
  239. * for the principal's password. The Subject will be populated
  240. * with the principal's key either from the keytab or derived from the
  241. * password entered.
  242. * <ul>
  243. * <p> <code>useKeyTab</code> = true
  244. * <code>keyTab</code>=< keytabname >
  245. * <code>storeKey</code>=true</code>
  246. * <code>doNotPrompt</code>=true;
  247. *</ul>
  248. * <p>The user will be prompted for the service principal name.
  249. * If the principal's
  250. * longterm key is available in the keytab , it will be added to the
  251. * Subject's private credentials. An authentication exchange will be
  252. * attempted with the principal name and the key from the Keytab.
  253. * If successful the TGT will be added to the
  254. * Subject's private credentials set. Otherwise the authentication will
  255. * fail.
  256. *<ul>
  257. * <p><code>useKeyTab</code> = true
  258. * <code>keyTab</code>=< file name > <code>storeKey</code>=true
  259. * <code>principal</code>= < principal name >
  260. * <code>useTicketCache</code>=true
  261. * <code>ticketCache</code>=< file name >
  262. *</ul>
  263. * <p>The principal's key will be retrieved from the keytab and added
  264. * to the <code>Subject</code>'s private credentials. If the key
  265. * is not available, the
  266. * user will be prompted for the password; the key derived from the password
  267. * will be added to the Subject's private credentials set. The
  268. * client's TGT will be retrieved from the ticket cache and added to the
  269. * <code>Subject</code>'s private credentials. If the TGT is not available
  270. * in the ticket cache, it will be obtained using the authentication
  271. * exchange and added to the Subject's private credentials.
  272. *
  273. *
  274. * @version 1.18, 01/11/00
  275. * @author Ram Marti
  276. */
  277. public class Krb5LoginModule implements LoginModule {
  278. // initial state
  279. private Subject subject;
  280. private CallbackHandler callbackHandler;
  281. private Map sharedState;
  282. private Map options;
  283. // configurable option
  284. private boolean debug = false;
  285. private boolean storeKey = false;
  286. private boolean doNotPrompt = false;
  287. private boolean useTicketCache = false;
  288. private boolean useKeyTab = false;
  289. private String ticketCacheName = null;
  290. private String keyTabName = null;
  291. private String princName = null;
  292. private boolean useFirstPass = false;
  293. private boolean tryFirstPass = false;
  294. private boolean storePass = false;
  295. private boolean clearPass = false;
  296. private boolean refreshKrb5Config = false;
  297. // the authentication status
  298. private boolean succeeded = false;
  299. private boolean commitSucceeded = false;
  300. private String username;
  301. private EncryptionKey encKey;
  302. private sun.security.krb5.Credentials cred = null;
  303. private PrincipalName principal = null;
  304. private KerberosPrincipal kerbClientPrinc = null;
  305. private KerberosTicket kerbTicket = null;
  306. private KerberosKey kerbKey = null;
  307. private StringBuffer krb5PrincName = null;
  308. private char[] password = null;
  309. private static final String NAME = "javax.security.auth.login.name";
  310. private static final String PWD = "javax.security.auth.login.password";
  311. static final java.util.ResourceBundle rb =
  312. java.util.ResourceBundle.getBundle("sun.security.util.AuthResources");
  313. /**
  314. * Initialize this <code>LoginModule</code>.
  315. *
  316. * <p>
  317. * @param subject the <code>Subject</code> to be authenticated. <p>
  318. *
  319. * @param callbackHandler a <code>CallbackHandler</code> for
  320. * communication with the end user (prompting for
  321. * usernames and passwords, for example). <p>
  322. *
  323. * @param sharedState shared <code>LoginModule</code> state. <p>
  324. *
  325. * @param options options specified in the login
  326. * <code>Configuration</code> for this particular
  327. * <code>LoginModule</code>.
  328. */
  329. public void initialize(Subject subject,
  330. CallbackHandler callbackHandler,
  331. Map sharedState, Map options) {
  332. this.subject = subject;
  333. this.callbackHandler = callbackHandler;
  334. this.sharedState = sharedState;
  335. this.options = options;
  336. // initialize any configured options
  337. debug = "true".equalsIgnoreCase((String)options.get("debug"));
  338. storeKey = "true".equalsIgnoreCase((String)options.get("storeKey"));
  339. doNotPrompt = "true".equalsIgnoreCase((String)options.get
  340. ("doNotPrompt"));
  341. useTicketCache = "true".equalsIgnoreCase((String)options.get
  342. ("useTicketCache"));
  343. useKeyTab = "true".equalsIgnoreCase((String)options.get("useKeyTab"));
  344. ticketCacheName = (String)options.get("ticketCache");
  345. keyTabName = (String)options.get("keyTab");
  346. princName = (String)options.get("principal");
  347. refreshKrb5Config =
  348. "true".equalsIgnoreCase((String)options.get("refreshKrb5Config"));
  349. tryFirstPass =
  350. "true".equalsIgnoreCase
  351. ((String)options.get("tryFirstPass"));
  352. useFirstPass =
  353. "true".equalsIgnoreCase
  354. ((String)options.get("useFirstPass"));
  355. storePass =
  356. "true".equalsIgnoreCase((String)options.get("storePass"));
  357. clearPass =
  358. "true".equalsIgnoreCase((String)options.get("clearPass"));
  359. if (debug) {
  360. System.out.print("Debug is " + debug
  361. + " storeKey " + storeKey
  362. + " useTicketCache " + useTicketCache
  363. + " useKeyTab " + useKeyTab
  364. + " doNotPrompt " + doNotPrompt
  365. + " ticketCache is " + ticketCacheName
  366. + " KeyTab is " + keyTabName
  367. + " refreshKrb5Config is " + refreshKrb5Config
  368. + " principal is " + princName
  369. + " tryFirstPass is " + tryFirstPass
  370. + " useFirstPass is " + useFirstPass
  371. + " storePass is " + storePass
  372. + " clearPass is " + clearPass + "\n");
  373. }
  374. }
  375. /**
  376. * Authenticate the user
  377. *
  378. * <p>
  379. *
  380. * @return true in all cases since this <code>LoginModule</code>
  381. * should not be ignored.
  382. *
  383. * @exception FailedLoginException if the authentication fails. <p>
  384. *
  385. * @exception LoginException if this <code>LoginModule</code>
  386. * is unable to perform the authentication.
  387. */
  388. public boolean login() throws LoginException {
  389. int len;
  390. validateConfiguration();
  391. if (refreshKrb5Config) {
  392. try {
  393. if (debug) {
  394. System.out.println("Refreshing Kerberos configuration");
  395. }
  396. sun.security.krb5.Config.refresh();
  397. } catch (KrbException ke) {
  398. LoginException le = new LoginException(ke.getMessage());
  399. le.initCause(ke);
  400. throw le;
  401. }
  402. }
  403. String principalProperty = System.getProperty
  404. ("sun.security.krb5.principal");
  405. if (principalProperty != null) {
  406. krb5PrincName = new StringBuffer(principalProperty);
  407. } else {
  408. if (princName != null) {
  409. krb5PrincName = new StringBuffer(princName);
  410. }
  411. }
  412. if (tryFirstPass) {
  413. try {
  414. attemptAuthentication(true);
  415. if (debug)
  416. System.out.println("\t\t[Krb5LoginModule] " +
  417. "authentication succeeded");
  418. succeeded = true;
  419. cleanState();
  420. return true;
  421. } catch (LoginException le) {
  422. // authentication failed -- try again below by prompting
  423. cleanState();
  424. if (debug) {
  425. System.out.println("\t\t[Krb5LoginModule] " +
  426. "tryFirstPass failed with:" +
  427. le.getMessage());
  428. }
  429. }
  430. } else if (useFirstPass) {
  431. try {
  432. attemptAuthentication(true);
  433. succeeded = true;
  434. cleanState();
  435. return true;
  436. } catch (LoginException e) {
  437. // authentication failed -- clean out state
  438. if (debug) {
  439. System.out.println("\t\t[Krb5LoginModule] " +
  440. "authentication failed \n" +
  441. e.getMessage());
  442. }
  443. succeeded = false;
  444. cleanState();
  445. throw e;
  446. }
  447. }
  448. // attempt the authentication by getting the username and pwd
  449. // by prompting or configuration i.e. not from shared state
  450. try {
  451. attemptAuthentication(false);
  452. succeeded = true;
  453. cleanState();
  454. return true;
  455. } catch (LoginException e) {
  456. // authentication failed -- clean out state
  457. if (debug) {
  458. System.out.println("\t\t[Krb5LoginModule] " +
  459. "authentication failed \n" +
  460. e.getMessage());
  461. }
  462. succeeded = false;
  463. cleanState();
  464. throw e;
  465. }
  466. }
  467. /**
  468. * process the configuration options
  469. * Get the TGT either out of
  470. * cache or from the KDC using the password entered
  471. * Check the permission before getting the TGT
  472. */
  473. private void attemptAuthentication(boolean getPasswdFromSharedState)
  474. throws LoginException {
  475. /*
  476. * Check the creds cache to see whether
  477. * we have TGT for this client principal
  478. */
  479. if (krb5PrincName != null) {
  480. try {
  481. principal = new PrincipalName
  482. (krb5PrincName.toString(),
  483. PrincipalName.KRB_NT_PRINCIPAL);
  484. } catch (KrbException e) {
  485. LoginException le = new LoginException(e.getMessage());
  486. le.initCause(e);
  487. throw le;
  488. }
  489. }
  490. try {
  491. if (useTicketCache) {
  492. // ticketCacheName == null implies the default cache
  493. cred = Credentials.acquireTGTFromCache
  494. (principal, ticketCacheName);
  495. if (cred != null) {
  496. // get the principal name from the ticket cache
  497. if (principal == null) {
  498. principal = cred.getClient();
  499. }
  500. }
  501. if (debug) {
  502. System.out.println("Principal is " + principal);
  503. if (cred == null) {
  504. System.out.println
  505. ("null credentials from Ticket Cache");
  506. }
  507. }
  508. }
  509. // cred = null indicates that we did n't get the creds
  510. // from the cache or useTicketCache was false
  511. if (cred == null) {
  512. // We need the principal name whether we use keytab
  513. // or AS Exchange
  514. if (principal == null) {
  515. promptForName(getPasswdFromSharedState);
  516. principal = new PrincipalName
  517. (krb5PrincName.toString(),
  518. PrincipalName.KRB_NT_PRINCIPAL);
  519. }
  520. if (useKeyTab) {
  521. encKey = EncryptionKey.acquireSecretKey
  522. (principal, keyTabName);
  523. if (debug) {
  524. if (encKey != null)
  525. System.out.println
  526. ("principal's key obtained from the keytab");
  527. else
  528. System.out.println
  529. ("Key for the principal " +
  530. principal +
  531. " not available in " +
  532. ((keyTabName == null) ?
  533. "default key tab" : keyTabName));
  534. }
  535. }
  536. // We can't get the key from the keytab so prompt
  537. if (encKey == null) {
  538. promptForPass(getPasswdFromSharedState);
  539. encKey = new EncryptionKey(
  540. new StringBuffer().append(password),
  541. principal.getSalt());
  542. }
  543. // Get the TGT using AS Exchange
  544. if (debug)
  545. System.out.println("principal is " + principal);
  546. cred = Credentials.acquireTGT(principal, encKey);
  547. // we should hava a non-null cred
  548. if (cred == null) {
  549. throw new LoginException
  550. ("TGT Can not be obtained from the KDC ");
  551. }
  552. }
  553. } catch (KrbException e) {
  554. LoginException le = new LoginException(e.getMessage());
  555. le.initCause(e);
  556. throw le;
  557. } catch (IOException ioe) {
  558. LoginException ie = new LoginException(ioe.getMessage());
  559. ie.initCause(ioe);
  560. throw ie;
  561. }
  562. }
  563. private void promptForName(boolean getPasswdFromSharedState)
  564. throws LoginException {
  565. krb5PrincName = new StringBuffer("");
  566. if (getPasswdFromSharedState) {
  567. // use the name saved by the first module in the stack
  568. username = (String)sharedState.get(NAME);
  569. if (debug) {
  570. System.out.println
  571. ("username from shared state is " + username + "\n");
  572. }
  573. if (username == null) {
  574. System.out.println
  575. ("username from shared state is null\n");
  576. throw new LoginException
  577. ("Username can not be obtained from sharedstate ");
  578. }
  579. if (debug) {
  580. System.out.println
  581. ("username from shared state is " + username + "\n");
  582. }
  583. if (username != null && username.length() > 0) {
  584. krb5PrincName.insert(0, username);
  585. return;
  586. }
  587. }
  588. if (doNotPrompt) {
  589. throw new LoginException
  590. ("Unable to obtain Princpal Name for authentication ");
  591. } else {
  592. if (callbackHandler == null)
  593. throw new LoginException("No CallbackHandler "
  594. + "available "
  595. + "to garner authentication "
  596. + "information from the user");
  597. try {
  598. String defUsername = System.getProperty("user.name");
  599. Callback[] callbacks = new Callback[1];
  600. MessageFormat form = new MessageFormat(
  601. rb.getString(
  602. "Kerberos username [[defUsername]]: "));
  603. Object[] source = {defUsername};
  604. callbacks[0] = new NameCallback(form.format(source));
  605. callbackHandler.handle(callbacks);
  606. username = ((NameCallback)callbacks[0]).getName();
  607. if (username == null || username.length() == 0)
  608. username = defUsername;
  609. krb5PrincName.insert(0, username);
  610. } catch (java.io.IOException ioe) {
  611. throw new LoginException(ioe.getMessage());
  612. } catch (UnsupportedCallbackException uce) {
  613. throw new LoginException
  614. (uce.getMessage()
  615. +" not available to garner "
  616. +" authentication information "
  617. +" from the user");
  618. }
  619. }
  620. }
  621. private void promptForPass(boolean getPasswdFromSharedState)
  622. throws LoginException {
  623. if (getPasswdFromSharedState) {
  624. // use the password saved by the first module in the stack
  625. password = (char[])sharedState.get(PWD);
  626. if (password == null) {
  627. if (debug) {
  628. System.out.println
  629. ("Password from shared state is null");
  630. }
  631. throw new LoginException
  632. ("Password can not be obtained from sharedstate ");
  633. }
  634. if (debug) {
  635. System.out.println
  636. ("password is " + new String(password));
  637. }
  638. return;
  639. }
  640. if (doNotPrompt) {
  641. throw new LoginException
  642. ("Unable to obtain password from user\n");
  643. } else {
  644. try {
  645. Callback[] callbacks = new Callback[1];
  646. String userName = krb5PrincName.toString();
  647. MessageFormat form = new MessageFormat(
  648. rb.getString(
  649. "Kerberos password for [username]: "));
  650. Object[] source = {userName};
  651. callbacks[0] = new PasswordCallback(
  652. form.format(source),
  653. false);
  654. callbackHandler.handle(callbacks);
  655. char[] tmpPassword = ((PasswordCallback)
  656. callbacks[0]).getPassword();
  657. if (tmpPassword == null) {
  658. // treat a NULL password as an empty password
  659. tmpPassword = new char[0];
  660. }
  661. password = new char[tmpPassword.length];
  662. System.arraycopy(tmpPassword, 0,
  663. password, 0, tmpPassword.length);
  664. ((PasswordCallback)callbacks[0]).clearPassword();
  665. // clear tmpPassword
  666. for (int i = 0; i < tmpPassword.length; i++)
  667. tmpPassword[i] = ' ';
  668. tmpPassword = null;
  669. if (debug) {
  670. System.out.println("\t\t[Krb5LoginModule] " +
  671. "user entered username: " +
  672. krb5PrincName);
  673. System.out.println();
  674. }
  675. } catch (java.io.IOException ioe) {
  676. throw new LoginException(ioe.getMessage());
  677. } catch (UnsupportedCallbackException uce) {
  678. throw new LoginException(uce.getMessage()
  679. +" not available to garner "
  680. +" authentication information "
  681. + "from the user");
  682. }
  683. }
  684. }
  685. private void validateConfiguration() throws LoginException {
  686. if (doNotPrompt && !useTicketCache && !useKeyTab)
  687. throw new LoginException
  688. ("Configuration Error"
  689. + " - either doNotPrompt should be "
  690. + " false or useTicketCache/useKeyTab "
  691. + " should be true");
  692. if (ticketCacheName != null && !useTicketCache)
  693. throw new LoginException
  694. ("Configuration Error "
  695. + " - useTicketCache should be set "
  696. + "to true to use the ticket cache"
  697. + ticketCacheName);
  698. if (keyTabName != null & !useKeyTab)
  699. throw new LoginException
  700. ("Configuration Error - useKeyTab should be set to true "
  701. + "to use the keytab" + keyTabName);
  702. if (storeKey && doNotPrompt && !useKeyTab)
  703. throw new LoginException
  704. ("Configuration Error - either doNotPrompt "
  705. + "should be set to false or "
  706. + "useKeyTab must be set to true for storeKey option");
  707. }
  708. /**
  709. * <p> This method is called if the LoginContext's
  710. * overall authentication succeeded
  711. * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
  712. * LoginModules succeeded).
  713. *
  714. * <p> If this LoginModule's own authentication attempt
  715. * succeeded (checked by retrieving the private state saved by the
  716. * <code>login</code> method), then this method associates a
  717. * <code>Krb5Principal</code>
  718. * with the <code>Subject</code> located in the
  719. * <code>LoginModule</code>. It adds Kerberos Credentials to the
  720. * the Subject's private credentials set. If this LoginModule's own
  721. * authentication attempted failed, then this method removes
  722. * any state that was originally saved.
  723. *
  724. * <p>
  725. *
  726. * @exception LoginException if the commit fails.
  727. *
  728. * @return true if this LoginModule's own login and commit
  729. * attempts succeeded, or false otherwise.
  730. */
  731. public boolean commit() throws LoginException {
  732. /*
  733. * Let us add the Krb5 Creds to the Subject's
  734. * private credentials. The credentials are of type
  735. * KerberosKey or KerberosTicket
  736. */
  737. if (succeeded == false) {
  738. return false;
  739. } else {
  740. /*
  741. * Add the Principal (authenticated identity)
  742. * to the Subject's principal set and
  743. * add the credentials (TGT or Service key) to the
  744. * Subject's private credentials
  745. */
  746. Set privCredSet = subject.getPrivateCredentials();
  747. Set princSet = subject.getPrincipals();
  748. kerbClientPrinc = new KerberosPrincipal(principal.getName());
  749. if (cred == null) {
  750. succeeded = false;
  751. throw new LoginException("Null Client Credential");
  752. }
  753. EncryptionKey sessionKey = cred.getSessionKey();
  754. kerbTicket = new KerberosTicket
  755. (cred.getEncoded(),
  756. new KerberosPrincipal(cred.getClient().getName()),
  757. new KerberosPrincipal(cred.getServer().getName()),
  758. sessionKey.getBytes(),
  759. sessionKey.getEType(),
  760. cred.getFlags(),
  761. cred.getAuthTime(),
  762. cred.getStartTime(),
  763. cred.getEndTime(),
  764. cred.getRenewTill(),
  765. cred.getClientAddresses());
  766. if (storeKey) {
  767. if (encKey == null) {
  768. succeeded = false;
  769. throw new LoginException("Null Server Key ");
  770. }
  771. Integer temp = encKey.getKeyVersionNumber();
  772. kerbKey = new KerberosKey(kerbClientPrinc,
  773. encKey.getBytes(),
  774. encKey.getEType(),
  775. (temp == null?
  776. 0: temp.intValue()));
  777. }
  778. // Let us add the kerbClientPrinc,kerbTicket and kerbKey (if
  779. // storeKey is true)
  780. if (!princSet.contains(princSet))
  781. princSet.add(kerbClientPrinc);
  782. if (!privCredSet.contains(kerbTicket))
  783. privCredSet.add(kerbTicket);
  784. if (storeKey) {
  785. if (!privCredSet.contains(kerbKey)) {
  786. privCredSet.add(kerbKey);
  787. }
  788. encKey.destroy();
  789. encKey = null;
  790. if (debug) {
  791. System.out.println("Added server's key"
  792. + kerbKey);
  793. System.out.println("\t\t[Krb5LoginModule] " +
  794. "added Krb5Principal " +
  795. kerbClientPrinc.toString()
  796. + " to Subject");
  797. }
  798. }
  799. }
  800. commitSucceeded = true;
  801. if (debug)
  802. System.out.println("Commit Succeeded \n");
  803. return true;
  804. }
  805. /**
  806. * <p> This method is called if the LoginContext's
  807. * overall authentication failed.
  808. * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
  809. * LoginModules did not succeed).
  810. *
  811. * <p> If this LoginModule's own authentication attempt
  812. * succeeded (checked by retrieving the private state saved by the
  813. * <code>login</code> and <code>commit</code> methods),
  814. * then this method cleans up any state that was originally saved.
  815. *
  816. * <p>
  817. *
  818. * @exception LoginException if the abort fails.
  819. *
  820. * @return false if this LoginModule's own login and/or commit attempts
  821. * failed, and true otherwise.
  822. */
  823. public boolean abort() throws LoginException {
  824. if (succeeded == false) {
  825. return false;
  826. } else if (succeeded == true && commitSucceeded == false) {
  827. // login succeeded but overall authentication failed
  828. succeeded = false;
  829. username = null;
  830. try {
  831. if (kerbTicket != null)
  832. kerbTicket.destroy();
  833. if (kerbKey != null)
  834. kerbKey.destroy();
  835. } catch (DestroyFailedException e) {
  836. throw new LoginException
  837. ("Destroy Failed on Kerberos Private Credentials");
  838. }
  839. kerbTicket = null;
  840. kerbKey = null;
  841. kerbClientPrinc = null;
  842. } else {
  843. // overall authentication succeeded and commit succeeded,
  844. // but someone else's commit failed
  845. logout();
  846. }
  847. return true;
  848. }
  849. /**
  850. * Logout the user.
  851. *
  852. * <p> This method removes the <code>Krb5Principal</code>
  853. * that was added by the <code>commit</code> method.
  854. *
  855. * <p>
  856. *
  857. * @exception LoginException if the logout fails.
  858. *
  859. * @return true in all cases since this <code>LoginModule</code>
  860. * should not be ignored.
  861. */
  862. public boolean logout() throws LoginException {
  863. subject.getPrincipals().remove(kerbClientPrinc);
  864. // Let us remove all Kerberos credentials stored in the Subject
  865. Iterator it = subject.getPrivateCredentials().iterator();
  866. while (it.hasNext()) {
  867. Object o = it.next();
  868. if (o instanceof KerberosTicket ||
  869. o instanceof KerberosKey) {
  870. it.remove();
  871. }
  872. }
  873. // Clean the slate
  874. try {
  875. if (kerbTicket != null)
  876. kerbTicket.destroy();
  877. if (kerbKey != null)
  878. kerbKey.destroy();
  879. } catch (DestroyFailedException e) {
  880. throw new LoginException
  881. ("Destroy Failed on Kerberos Private Credentials");
  882. }
  883. kerbTicket = null;
  884. kerbKey = null;
  885. kerbClientPrinc = null;
  886. succeeded = false;
  887. commitSucceeded = false;
  888. username = null;
  889. if (debug) {
  890. System.out.println("\t\t[Krb5LoginModule]: " +
  891. "logged out Subject");
  892. }
  893. return true;
  894. }
  895. /**
  896. * Clean out the state
  897. */
  898. private void cleanState() {
  899. // save input as shared state only if
  900. // authentication succeeded
  901. if (succeeded) {
  902. if (storePass &&
  903. !sharedState.containsKey(NAME) &&
  904. !sharedState.containsKey(PWD)) {
  905. sharedState.put(NAME, username);
  906. sharedState.put(PWD, password);
  907. }
  908. }
  909. username = null;
  910. password = null;
  911. if (krb5PrincName != null && krb5PrincName.length() != 0)
  912. krb5PrincName.delete(0, krb5PrincName.length());
  913. krb5PrincName = null;
  914. if (clearPass) {
  915. sharedState.remove(NAME);
  916. sharedState.remove(PWD);
  917. }
  918. }
  919. }