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