1. /*
  2. * @(#)KeyStoreLoginModule.java 1.18 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 javax.security.auth.x500.X500Principal;
  9. import java.io.File;
  10. import java.io.IOException;
  11. import java.io.InputStream;
  12. import java.io.PushbackInputStream;
  13. import java.net.MalformedURLException;
  14. import java.net.URL;
  15. import java.security.AuthProvider;
  16. import java.security.GeneralSecurityException;
  17. import java.security.Key;
  18. import java.security.KeyStore;
  19. import java.security.KeyStoreException;
  20. import java.security.NoSuchAlgorithmException;
  21. import java.security.NoSuchProviderException;
  22. import java.security.Principal;
  23. import java.security.PrivateKey;
  24. import java.security.Provider;
  25. import java.security.UnrecoverableKeyException;
  26. import java.security.cert.*;
  27. import java.security.cert.X509Certificate;
  28. import java.util.Arrays;
  29. import java.util.Iterator;
  30. import java.util.LinkedList;
  31. import java.util.Map;
  32. import java.util.ResourceBundle;
  33. import javax.security.auth.Destroyable;
  34. import javax.security.auth.DestroyFailedException;
  35. import javax.security.auth.Subject;
  36. import javax.security.auth.x500.*;
  37. import javax.security.auth.Subject;
  38. import javax.security.auth.x500.*;
  39. import javax.security.auth.callback.Callback;
  40. import javax.security.auth.callback.CallbackHandler;
  41. import javax.security.auth.callback.ConfirmationCallback;
  42. import javax.security.auth.callback.NameCallback;
  43. import javax.security.auth.callback.PasswordCallback;
  44. import javax.security.auth.callback.TextOutputCallback;
  45. import javax.security.auth.callback.UnsupportedCallbackException;
  46. import javax.security.auth.login.FailedLoginException;
  47. import javax.security.auth.login.LoginException;
  48. import javax.security.auth.spi.LoginModule;
  49. import sun.security.util.AuthResources;
  50. import sun.security.util.Password;
  51. /**
  52. * Provides a JAAS login module that prompts for a key store alias and
  53. * populates the subject with the alias's principal and credentials. Stores
  54. * an <code>X500Principal</code> for the subject distinguished name of the
  55. * first certificate in the alias's credentials in the subject's principals,
  56. * the alias's certificate path in the subject's public credentials, and a
  57. * <code>X500PrivateCredential</code> whose certificate is the first
  58. * certificate in the alias's certificate path and whose private key is the
  59. * alias's private key in the subject's private credentials. <p>
  60. *
  61. * Recognizes the following options in the configuration file:
  62. * <dl>
  63. *
  64. * <dt> <code>keyStoreURL</code> </dt>
  65. * <dd> A URL that specifies the location of the key store. Defaults to
  66. * a URL pointing to the .keystore file in the directory specified by the
  67. * <code>user.home</code> system property. The input stream from this
  68. * URL is passed to the <code>KeyStore.load</code> method.
  69. * "NONE" may be specified if a <code>null</code> stream must be
  70. * passed to the <code>KeyStore.load</code> method.
  71. * "NONE" should be specified if the KeyStore resides
  72. * on a hardware token device, for example.</dd>
  73. *
  74. * <dt> <code>keyStoreType</code> </dt>
  75. * <dd> The key store type. If not specified, defaults to the result of
  76. * calling <code>KeyStore.getDefaultType()</code>.
  77. * If the type is "PKCS11", then keyStoreURL must be "NONE"
  78. * and privateKeyPasswordURL must not be specified.</dd>
  79. *
  80. * <dt> <code>keyStoreProvider</code> </dt>
  81. * <dd> The key store provider. If not specified, uses the standard search
  82. * order to find the provider. </dd>
  83. *
  84. * <dt> <code>keyStoreAlias</code> </dt>
  85. * <dd> The alias in the key store to login as. Required when no callback
  86. * handler is provided. No default value. </dd>
  87. *
  88. * <dt> <code>keyStorePasswordURL</code> </dt>
  89. * <dd> A URL that specifies the location of the key store password. Required
  90. * when no callback handler is provided and
  91. * <code>protected</code> is false.
  92. * No default value. </dd>
  93. *
  94. * <dt> <code>privateKeyPasswordURL</code> </dt>
  95. * <dd> A URL that specifies the location of the specific private key password
  96. * needed to access the private key for this alias.
  97. * The keystore password
  98. * is used if this value is needed and not specified. </dd>
  99. *
  100. * <dt> <code>protected</code> </dt>
  101. * <dd> This value should be set to "true" if the KeyStore
  102. * has a separate, protected authentication path
  103. * (for example, a dedicated PIN-pad attached to a smart card).
  104. * Defaults to "false". If "true" keyStorePasswordURL and
  105. * privateKeyPasswordURL must not be specified.</dd>
  106. *
  107. * </dl>
  108. */
  109. public class KeyStoreLoginModule implements LoginModule {
  110. static final java.util.ResourceBundle rb =
  111. java.util.ResourceBundle.getBundle("sun.security.util.AuthResources");
  112. /* -- Fields -- */
  113. private static final int UNINITIALIZED = 0;
  114. private static final int INITIALIZED = 1;
  115. private static final int AUTHENTICATED = 2;
  116. private static final int LOGGED_IN = 3;
  117. private static final int PROTECTED_PATH = 0;
  118. private static final int TOKEN = 1;
  119. private static final int NORMAL = 2;
  120. private static final String NONE = "NONE";
  121. private static final String P11KEYSTORE = "PKCS11";
  122. private static final TextOutputCallback bannerCallback =
  123. new TextOutputCallback
  124. (TextOutputCallback.INFORMATION,
  125. rb.getString("Please enter keystore information"));
  126. private final ConfirmationCallback confirmationCallback =
  127. new ConfirmationCallback
  128. (ConfirmationCallback.INFORMATION,
  129. ConfirmationCallback.OK_CANCEL_OPTION,
  130. ConfirmationCallback.OK);
  131. private Subject subject;
  132. private CallbackHandler callbackHandler;
  133. private Map sharedState;
  134. private Map options;
  135. private char[] keyStorePassword;
  136. private char[] privateKeyPassword;
  137. private KeyStore keyStore;
  138. private String keyStoreURL;
  139. private String keyStoreType;
  140. private String keyStoreProvider;
  141. private String keyStoreAlias;
  142. private String keyStorePasswordURL;
  143. private String privateKeyPasswordURL;
  144. private boolean debug;
  145. private javax.security.auth.x500.X500Principal principal;
  146. private Certificate[] fromKeyStore;
  147. private java.security.cert.CertPath certP = null;
  148. private X500PrivateCredential privateCredential;
  149. private int status = UNINITIALIZED;
  150. private boolean nullStream = false;
  151. private boolean token = false;
  152. private boolean protectedPath = false;
  153. /* -- Methods -- */
  154. /**
  155. * Initialize this <code>LoginModule</code>.
  156. *
  157. * <p>
  158. *
  159. * @param subject the <code>Subject</code> to be authenticated. <p>
  160. *
  161. * @param callbackHandler a <code>CallbackHandler</code> for communicating
  162. * with the end user (prompting for usernames and
  163. * passwords, for example),
  164. * which may be <code>null</code>. <p>
  165. *
  166. * @param sharedState shared <code>LoginModule</code> state. <p>
  167. *
  168. * @param options options specified in the login
  169. * <code>Configuration</code> for this particular
  170. * <code>LoginModule</code>.
  171. */
  172. public void initialize(Subject subject,
  173. CallbackHandler callbackHandler,
  174. Map<String,?> sharedState,
  175. Map<String,?> options)
  176. {
  177. this.subject = subject;
  178. this.callbackHandler = callbackHandler;
  179. this.sharedState = sharedState;
  180. this.options = options;
  181. processOptions();
  182. status = INITIALIZED;
  183. }
  184. private void processOptions() {
  185. keyStoreURL = (String) options.get("keyStoreURL");
  186. if (keyStoreURL == null) {
  187. keyStoreURL =
  188. "file:" +
  189. System.getProperty("user.home").replace(
  190. File.separatorChar, '/') +
  191. '/' + ".keystore";
  192. } else if (NONE.equals(keyStoreURL)) {
  193. nullStream = true;
  194. }
  195. keyStoreType = (String) options.get("keyStoreType");
  196. if (keyStoreType == null) {
  197. keyStoreType = KeyStore.getDefaultType();
  198. }
  199. if (P11KEYSTORE.equalsIgnoreCase(keyStoreType)) {
  200. token = true;
  201. }
  202. keyStoreProvider = (String) options.get("keyStoreProvider");
  203. keyStoreAlias = (String) options.get("keyStoreAlias");
  204. keyStorePasswordURL = (String) options.get("keyStorePasswordURL");
  205. privateKeyPasswordURL = (String) options.get("privateKeyPasswordURL");
  206. protectedPath = "true".equalsIgnoreCase((String)options.get
  207. ("protected"));
  208. debug = "true".equalsIgnoreCase((String) options.get("debug"));
  209. if (debug) {
  210. debugPrint(null);
  211. debugPrint("keyStoreURL=" + keyStoreURL);
  212. debugPrint("keyStoreType=" + keyStoreType);
  213. debugPrint("keyStoreProvider=" + keyStoreProvider);
  214. debugPrint("keyStoreAlias=" + keyStoreAlias);
  215. debugPrint("keyStorePasswordURL=" + keyStorePasswordURL);
  216. debugPrint("privateKeyPasswordURL=" + privateKeyPasswordURL);
  217. debugPrint("protectedPath=" + protectedPath);
  218. debugPrint(null);
  219. }
  220. }
  221. /**
  222. * Authenticate the user.
  223. *
  224. * <p> Get the Keystore alias and relevant passwords.
  225. * Retrieve the alias's principal and credentials from the Keystore.
  226. *
  227. * <p>
  228. *
  229. * @exception FailedLoginException if the authentication fails. <p>
  230. *
  231. * @return true in all cases (this <code>LoginModule</code>
  232. * should not be ignored).
  233. */
  234. public boolean login() throws LoginException {
  235. switch (status) {
  236. case UNINITIALIZED:
  237. default:
  238. throw new LoginException("The login module is not initialized");
  239. case INITIALIZED:
  240. case AUTHENTICATED:
  241. if (token && !nullStream) {
  242. throw new LoginException
  243. ("if keyStoreType is " + P11KEYSTORE +
  244. " then keyStoreURL must be " + NONE);
  245. }
  246. if (token && privateKeyPasswordURL != null) {
  247. throw new LoginException
  248. ("if keyStoreType is " + P11KEYSTORE +
  249. " then privateKeyPasswordURL must not be specified");
  250. }
  251. if (protectedPath &&
  252. (keyStorePasswordURL != null ||
  253. privateKeyPasswordURL != null)) {
  254. throw new LoginException
  255. ("if protected is true then keyStorePasswordURL and " +
  256. "privateKeyPasswordURL must not be specified");
  257. }
  258. // get relevant alias and password info
  259. if (protectedPath) {
  260. getAliasAndPasswords(PROTECTED_PATH);
  261. } else if (token) {
  262. getAliasAndPasswords(TOKEN);
  263. } else {
  264. getAliasAndPasswords(NORMAL);
  265. }
  266. // log into KeyStore to retrieve data,
  267. // then clear passwords
  268. try {
  269. getKeyStoreInfo();
  270. } finally {
  271. if (privateKeyPassword != null &&
  272. privateKeyPassword != keyStorePassword) {
  273. Arrays.fill(privateKeyPassword, '\0');
  274. privateKeyPassword = null;
  275. }
  276. if (keyStorePassword != null) {
  277. Arrays.fill(keyStorePassword, '\0');
  278. keyStorePassword = null;
  279. }
  280. }
  281. status = AUTHENTICATED;
  282. return true;
  283. case LOGGED_IN:
  284. return true;
  285. }
  286. }
  287. /** Get the alias and passwords to use for looking up in the KeyStore. */
  288. private void getAliasAndPasswords(int env) throws LoginException {
  289. if (callbackHandler == null) {
  290. // No callback handler - check for alias and password options
  291. switch (env) {
  292. case PROTECTED_PATH:
  293. checkAlias();
  294. break;
  295. case TOKEN:
  296. checkAlias();
  297. checkStorePass();
  298. break;
  299. case NORMAL:
  300. checkAlias();
  301. checkStorePass();
  302. checkKeyPass();
  303. break;
  304. }
  305. } else {
  306. // Callback handler available - prompt for alias and passwords
  307. NameCallback aliasCallback;
  308. if (keyStoreAlias == null || keyStoreAlias.length() == 0) {
  309. aliasCallback = new NameCallback(
  310. rb.getString("Keystore alias: "));
  311. } else {
  312. aliasCallback =
  313. new NameCallback(rb.getString("Keystore alias: "),
  314. keyStoreAlias);
  315. }
  316. PasswordCallback storePassCallback = null;
  317. PasswordCallback keyPassCallback = null;
  318. switch (env) {
  319. case PROTECTED_PATH:
  320. break;
  321. case NORMAL:
  322. keyPassCallback = new PasswordCallback
  323. (rb.getString("Private key password (optional): "), false);
  324. // fall thru
  325. case TOKEN:
  326. storePassCallback = new PasswordCallback
  327. (rb.getString("Keystore password: "), false);
  328. break;
  329. }
  330. prompt(aliasCallback, storePassCallback, keyPassCallback);
  331. }
  332. if (debug) {
  333. debugPrint("alias=" + keyStoreAlias);
  334. }
  335. }
  336. private void checkAlias() throws LoginException {
  337. if (keyStoreAlias == null) {
  338. throw new LoginException
  339. ("Need to specify an alias option to use " +
  340. "KeyStoreLoginModule non-interactively.");
  341. }
  342. }
  343. private void checkStorePass() throws LoginException {
  344. if (keyStorePasswordURL == null) {
  345. throw new LoginException
  346. ("Need to specify keyStorePasswordURL option to use " +
  347. "KeyStoreLoginModule non-interactively.");
  348. }
  349. try {
  350. InputStream in = new URL(keyStorePasswordURL).openStream();
  351. keyStorePassword = Password.readPassword(in);
  352. in.close();
  353. } catch (IOException e) {
  354. LoginException le = new LoginException
  355. ("Problem accessing keystore password \"" +
  356. keyStorePasswordURL + "\"");
  357. le.initCause(e);
  358. throw le;
  359. }
  360. }
  361. private void checkKeyPass() throws LoginException {
  362. if (privateKeyPasswordURL == null) {
  363. privateKeyPassword = keyStorePassword;
  364. } else {
  365. try {
  366. InputStream in = new URL(privateKeyPasswordURL).openStream();
  367. privateKeyPassword = Password.readPassword(in);
  368. in.close();
  369. } catch (IOException e) {
  370. LoginException le = new LoginException
  371. ("Problem accessing private key password \"" +
  372. privateKeyPasswordURL + "\"");
  373. le.initCause(e);
  374. throw le;
  375. }
  376. }
  377. }
  378. private void prompt(NameCallback aliasCallback,
  379. PasswordCallback storePassCallback,
  380. PasswordCallback keyPassCallback)
  381. throws LoginException {
  382. if (storePassCallback == null) {
  383. // only prompt for alias
  384. try {
  385. callbackHandler.handle(
  386. new Callback[] {
  387. bannerCallback, aliasCallback, confirmationCallback
  388. });
  389. } catch (IOException e) {
  390. LoginException le = new LoginException
  391. ("Problem retrieving keystore alias");
  392. le.initCause(e);
  393. throw le;
  394. } catch (UnsupportedCallbackException e) {
  395. throw new LoginException(
  396. "Error: " + e.getCallback().toString() +
  397. " is not available to retrieve authentication " +
  398. " information from the user");
  399. }
  400. int confirmationResult = confirmationCallback.getSelectedIndex();
  401. if (confirmationResult == ConfirmationCallback.CANCEL) {
  402. throw new LoginException("Login cancelled");
  403. }
  404. saveAlias(aliasCallback);
  405. } else if (keyPassCallback == null) {
  406. // prompt for alias and key store password
  407. try {
  408. callbackHandler.handle(
  409. new Callback[] {
  410. bannerCallback, aliasCallback,
  411. storePassCallback, confirmationCallback
  412. });
  413. } catch (IOException e) {
  414. LoginException le = new LoginException
  415. ("Problem retrieving keystore alias and password");
  416. le.initCause(e);
  417. throw le;
  418. } catch (UnsupportedCallbackException e) {
  419. throw new LoginException(
  420. "Error: " + e.getCallback().toString() +
  421. " is not available to retrieve authentication " +
  422. " information from the user");
  423. }
  424. int confirmationResult = confirmationCallback.getSelectedIndex();
  425. if (confirmationResult == ConfirmationCallback.CANCEL) {
  426. throw new LoginException("Login cancelled");
  427. }
  428. saveAlias(aliasCallback);
  429. saveStorePass(storePassCallback);
  430. } else {
  431. // prompt for alias, key store password, and key password
  432. try {
  433. callbackHandler.handle(
  434. new Callback[] {
  435. bannerCallback, aliasCallback,
  436. storePassCallback, keyPassCallback,
  437. confirmationCallback
  438. });
  439. } catch (IOException e) {
  440. LoginException le = new LoginException
  441. ("Problem retrieving keystore alias and passwords");
  442. le.initCause(e);
  443. throw le;
  444. } catch (UnsupportedCallbackException e) {
  445. throw new LoginException(
  446. "Error: " + e.getCallback().toString() +
  447. " is not available to retrieve authentication " +
  448. " information from the user");
  449. }
  450. int confirmationResult = confirmationCallback.getSelectedIndex();
  451. if (confirmationResult == ConfirmationCallback.CANCEL) {
  452. throw new LoginException("Login cancelled");
  453. }
  454. saveAlias(aliasCallback);
  455. saveStorePass(storePassCallback);
  456. saveKeyPass(keyPassCallback);
  457. }
  458. }
  459. private void saveAlias(NameCallback cb) {
  460. keyStoreAlias = cb.getName();
  461. }
  462. private void saveStorePass(PasswordCallback c) {
  463. keyStorePassword = c.getPassword();
  464. if (keyStorePassword == null) {
  465. /* Treat a NULL password as an empty password */
  466. keyStorePassword = new char[0];
  467. }
  468. c.clearPassword();
  469. }
  470. private void saveKeyPass(PasswordCallback c) {
  471. privateKeyPassword = c.getPassword();
  472. if (privateKeyPassword == null || privateKeyPassword.length == 0) {
  473. /*
  474. * Use keystore password if no private key password is
  475. * specified.
  476. */
  477. privateKeyPassword = keyStorePassword;
  478. }
  479. c.clearPassword();
  480. }
  481. /** Get the credentials from the KeyStore. */
  482. private void getKeyStoreInfo() throws LoginException {
  483. /* Get KeyStore instance */
  484. try {
  485. if (keyStoreProvider == null) {
  486. keyStore = KeyStore.getInstance(keyStoreType);
  487. } else {
  488. keyStore =
  489. KeyStore.getInstance(keyStoreType, keyStoreProvider);
  490. }
  491. } catch (KeyStoreException e) {
  492. LoginException le = new LoginException
  493. ("The specified keystore type was not available");
  494. le.initCause(e);
  495. throw le;
  496. } catch (NoSuchProviderException e) {
  497. LoginException le = new LoginException
  498. ("The specified keystore provider was not available");
  499. le.initCause(e);
  500. throw le;
  501. }
  502. /* Load KeyStore contents from file */
  503. try {
  504. if (nullStream) {
  505. // if using protected auth path, keyStorePassword will be null
  506. keyStore.load(null, keyStorePassword);
  507. } else {
  508. InputStream in = new URL(keyStoreURL).openStream();
  509. keyStore.load(in, keyStorePassword);
  510. in.close();
  511. }
  512. } catch (MalformedURLException e) {
  513. LoginException le = new LoginException
  514. ("Incorrect keyStoreURL option");
  515. le.initCause(e);
  516. throw le;
  517. } catch (GeneralSecurityException e) {
  518. LoginException le = new LoginException
  519. ("Error initializing keystore");
  520. le.initCause(e);
  521. throw le;
  522. } catch (IOException e) {
  523. LoginException le = new LoginException
  524. ("Error initializing keystore");
  525. le.initCause(e);
  526. throw le;
  527. }
  528. /* Get certificate chain and create a certificate path */
  529. try {
  530. fromKeyStore =
  531. keyStore.getCertificateChain(keyStoreAlias);
  532. if (fromKeyStore == null
  533. || fromKeyStore.length == 0
  534. || !(fromKeyStore[0] instanceof X509Certificate))
  535. {
  536. throw new FailedLoginException(
  537. "Unable to find X.509 certificate chain in keystore");
  538. } else {
  539. LinkedList certList = new LinkedList();
  540. for (int i=0; i < fromKeyStore.length; i++) {
  541. certList.add(fromKeyStore[i]);
  542. }
  543. CertificateFactory certF=
  544. CertificateFactory.getInstance("X.509");
  545. certP =
  546. certF.generateCertPath(certList);
  547. }
  548. } catch (KeyStoreException e) {
  549. LoginException le = new LoginException("Error using keystore");
  550. le.initCause(e);
  551. throw le;
  552. } catch (CertificateException ce) {
  553. LoginException le = new LoginException
  554. ("Error: X.509 Certificate type unavailable");
  555. le.initCause(ce);
  556. throw le;
  557. }
  558. /* Get principal and keys */
  559. try {
  560. X509Certificate certificate = (X509Certificate)fromKeyStore[0];
  561. principal = new javax.security.auth.x500.X500Principal
  562. (certificate.getSubjectDN().getName());
  563. // if token, privateKeyPassword will be null
  564. Key privateKey = keyStore.getKey(keyStoreAlias, privateKeyPassword);
  565. if (privateKey == null
  566. || !(privateKey instanceof PrivateKey))
  567. {
  568. throw new FailedLoginException(
  569. "Unable to recover key from keystore");
  570. }
  571. privateCredential = new X500PrivateCredential(
  572. certificate, (PrivateKey) privateKey, keyStoreAlias);
  573. } catch (KeyStoreException e) {
  574. LoginException le = new LoginException("Error using keystore");
  575. le.initCause(e);
  576. throw le;
  577. } catch (NoSuchAlgorithmException e) {
  578. LoginException le = new LoginException("Error using keystore");
  579. le.initCause(e);
  580. throw le;
  581. } catch (UnrecoverableKeyException e) {
  582. FailedLoginException fle = new FailedLoginException
  583. ("Unable to recover key from keystore");
  584. fle.initCause(e);
  585. throw fle;
  586. }
  587. if (debug) {
  588. debugPrint("principal=" + principal +
  589. "\n certificate="
  590. + privateCredential.getCertificate() +
  591. "\n alias =" + privateCredential.getAlias());
  592. }
  593. }
  594. /**
  595. * Abstract method to commit the authentication process (phase 2).
  596. *
  597. * <p> This method is called if the LoginContext's
  598. * overall authentication succeeded
  599. * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
  600. * succeeded).
  601. *
  602. * <p> If this LoginModule's own authentication attempt
  603. * succeeded (checked by retrieving the private state saved by the
  604. * <code>login</code> method), then this method associates a
  605. * <code>X500Principal</code> for the subject distinguished name of the
  606. * first certificate in the alias's credentials in the subject's
  607. * principals,the alias's certificate path in the subject's public
  608. * credentials, and a<code>X500PrivateCredential</code> whose certificate
  609. * is the first certificate in the alias's certificate path and whose
  610. * private key is the alias's private key in the subject's private
  611. * credentials. If this LoginModule's own
  612. * authentication attempted failed, then this method removes
  613. * any state that was originally saved.
  614. *
  615. * <p>
  616. *
  617. * @exception LoginException if the commit fails
  618. *
  619. * @return true if this LoginModule's own login and commit
  620. * attempts succeeded, or false otherwise.
  621. */
  622. public boolean commit() throws LoginException {
  623. switch (status) {
  624. case UNINITIALIZED:
  625. default:
  626. throw new LoginException("The login module is not initialized");
  627. case INITIALIZED:
  628. logoutInternal();
  629. throw new LoginException("Authentication failed");
  630. case AUTHENTICATED:
  631. if (commitInternal()) {
  632. return true;
  633. } else {
  634. logoutInternal();
  635. throw new LoginException("Unable to retrieve certificates");
  636. }
  637. case LOGGED_IN:
  638. return true;
  639. }
  640. }
  641. private boolean commitInternal() throws LoginException {
  642. /* If the subject is not readonly add to the principal and credentials
  643. * set; otherwise just return true
  644. */
  645. if (subject.isReadOnly()) {
  646. throw new LoginException ("Subject is set readonly");
  647. } else {
  648. subject.getPrincipals().add(principal);
  649. subject.getPublicCredentials().add(certP);
  650. subject.getPrivateCredentials().add(privateCredential);
  651. status = LOGGED_IN;
  652. return true;
  653. }
  654. }
  655. /**
  656. * <p> This method is called if the LoginContext's
  657. * overall authentication failed.
  658. * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
  659. * did not succeed).
  660. *
  661. * <p> If this LoginModule's own authentication attempt
  662. * succeeded (checked by retrieving the private state saved by the
  663. * <code>login</code> and <code>commit</code> methods),
  664. * then this method cleans up any state that was originally saved.
  665. *
  666. * <p> If the loaded KeyStore's provider extends
  667. * <code>java.security.AuthProvider</code>,
  668. * then the provider's <code>logout</code> method is invoked.
  669. *
  670. * <p>
  671. *
  672. * @exception LoginException if the abort fails.
  673. *
  674. * @return false if this LoginModule's own login and/or commit attempts
  675. * failed, and true otherwise.
  676. */
  677. public boolean abort() throws LoginException {
  678. switch (status) {
  679. case UNINITIALIZED:
  680. default:
  681. return false;
  682. case INITIALIZED:
  683. return false;
  684. case AUTHENTICATED:
  685. logoutInternal();
  686. return true;
  687. case LOGGED_IN:
  688. logoutInternal();
  689. return true;
  690. }
  691. }
  692. /**
  693. * Logout a user.
  694. *
  695. * <p> This method removes the Principals, public credentials and the
  696. * private credentials that were added by the <code>commit</code> method.
  697. *
  698. * <p> If the loaded KeyStore's provider extends
  699. * <code>java.security.AuthProvider</code>,
  700. * then the provider's <code>logout</code> method is invoked.
  701. *
  702. * <p>
  703. *
  704. * @exception LoginException if the logout fails.
  705. *
  706. * @return true in all cases since this <code>LoginModule</code>
  707. * should not be ignored.
  708. */
  709. public boolean logout() throws LoginException {
  710. if (debug)
  711. debugPrint("Entering logout " + status);
  712. switch (status) {
  713. case UNINITIALIZED:
  714. throw new LoginException
  715. ("The login module is not initialized");
  716. case INITIALIZED:
  717. case AUTHENTICATED:
  718. default:
  719. // impossible for LoginModule to be in AUTHENTICATED
  720. // state
  721. // assert status != AUTHENTICATED;
  722. return false;
  723. case LOGGED_IN:
  724. logoutInternal();
  725. return true;
  726. }
  727. }
  728. private void logoutInternal() throws LoginException {
  729. if (debug) {
  730. debugPrint("Entering logoutInternal");
  731. }
  732. // assumption is that KeyStore.load did a login -
  733. // perform explicit logout if possible
  734. LoginException logoutException = null;
  735. Provider provider = keyStore.getProvider();
  736. if (provider instanceof AuthProvider) {
  737. AuthProvider ap = (AuthProvider)provider;
  738. try {
  739. ap.logout();
  740. if (debug) {
  741. debugPrint("logged out of KeyStore AuthProvider");
  742. }
  743. } catch (LoginException le) {
  744. // save but continue below
  745. logoutException = le;
  746. }
  747. }
  748. if (subject.isReadOnly()) {
  749. // attempt to destroy the private credential
  750. // even if the Subject is read-only
  751. principal = null;
  752. certP = null;
  753. status = INITIALIZED;
  754. // destroy the private credential
  755. Iterator it = subject.getPrivateCredentials().iterator();
  756. while (it.hasNext()) {
  757. Object obj = it.next();
  758. if (privateCredential.equals(obj)) {
  759. privateCredential = null;
  760. try {
  761. ((Destroyable)obj).destroy();
  762. if (debug)
  763. debugPrint("Destroyed private credential, " +
  764. obj.getClass().getName());
  765. break;
  766. } catch (DestroyFailedException dfe) {
  767. LoginException le = new LoginException
  768. ("Unable to destroy private credential, "
  769. + obj.getClass().getName());
  770. le.initCause(dfe);
  771. throw le;
  772. }
  773. }
  774. }
  775. // throw an exception because we can not remove
  776. // the principal and public credential from this
  777. // read-only Subject
  778. throw new LoginException
  779. ("Unable to remove Principal ("
  780. + "X500Principal "
  781. + ") and public credential (certificatepath) "
  782. + "from read-only Subject");
  783. }
  784. if (principal != null) {
  785. subject.getPrincipals().remove(principal);
  786. principal = null;
  787. }
  788. if (certP != null) {
  789. subject.getPublicCredentials().remove(certP);
  790. certP = null;
  791. }
  792. if (privateCredential != null) {
  793. subject.getPrivateCredentials().remove(privateCredential);
  794. privateCredential = null;
  795. }
  796. // throw pending logout exception if there is one
  797. if (logoutException != null) {
  798. throw logoutException;
  799. }
  800. status = INITIALIZED;
  801. }
  802. private void debugPrint(String message) {
  803. // we should switch to logging API
  804. if (message == null) {
  805. System.err.println();
  806. } else {
  807. System.err.println("Debug KeyStoreLoginModule: " + message);
  808. }
  809. }
  810. }