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