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