jdk/src/share/classes/com/sun/security/auth/module/KeyStoreLoginModule.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/auth/module/KeyStoreLoginModule.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,948 @@
+/*
+ * Copyright 2000-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.security.auth.module;
+
+import javax.security.auth.x500.X500Principal;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AuthProvider;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.*;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.ResourceBundle;
+import javax.security.auth.Destroyable;
+import javax.security.auth.DestroyFailedException;
+import javax.security.auth.Subject;
+import javax.security.auth.x500.*;
+import javax.security.auth.Subject;
+import javax.security.auth.x500.*;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.ConfirmationCallback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import sun.security.util.AuthResources;
+import sun.security.util.Password;
+
+/**
+ * Provides a JAAS login module that prompts for a key store alias and
+ * populates the subject with the alias's principal and credentials. Stores
+ * an <code>X500Principal</code> for the subject distinguished name of the
+ * first certificate in the alias's credentials in the subject's principals,
+ * the alias's certificate path in the subject's public credentials, and a
+ * <code>X500PrivateCredential</code> whose certificate is the first
+ * certificate in the alias's certificate path and whose private key is the
+ * alias's private key in the subject's private credentials. <p>
+ *
+ * Recognizes the following options in the configuration file:
+ * <dl>
+ *
+ * <dt> <code>keyStoreURL</code> </dt>
+ * <dd> A URL that specifies the location of the key store.  Defaults to
+ *      a URL pointing to the .keystore file in the directory specified by the
+ *      <code>user.home</code> system property.  The input stream from this
+ *      URL is passed to the <code>KeyStore.load</code> method.
+ *      "NONE" may be specified if a <code>null</code> stream must be
+ *      passed to the <code>KeyStore.load</code> method.
+ *      "NONE" should be specified if the KeyStore resides
+ *      on a hardware token device, for example.</dd>
+ *
+ * <dt> <code>keyStoreType</code> </dt>
+ * <dd> The key store type.  If not specified, defaults to the result of
+ *      calling <code>KeyStore.getDefaultType()</code>.
+ *      If the type is "PKCS11", then keyStoreURL must be "NONE"
+ *      and privateKeyPasswordURL must not be specified.</dd>
+ *
+ * <dt> <code>keyStoreProvider</code> </dt>
+ * <dd> The key store provider.  If not specified, uses the standard search
+ *      order to find the provider. </dd>
+ *
+ * <dt> <code>keyStoreAlias</code> </dt>
+ * <dd> The alias in the key store to login as.  Required when no callback
+ *      handler is provided.  No default value. </dd>
+ *
+ * <dt> <code>keyStorePasswordURL</code> </dt>
+ * <dd> A URL that specifies the location of the key store password.  Required
+ *      when no callback handler is provided and
+ *      <code>protected</code> is false.
+ *      No default value. </dd>
+ *
+ * <dt> <code>privateKeyPasswordURL</code> </dt>
+ * <dd> A URL that specifies the location of the specific private key password
+ *      needed to access the private key for this alias.
+ *      The keystore password
+ *      is used if this value is needed and not specified. </dd>
+ *
+ * <dt> <code>protected</code> </dt>
+ * <dd> This value should be set to "true" if the KeyStore
+ *      has a separate, protected authentication path
+ *      (for example, a dedicated PIN-pad attached to a smart card).
+ *      Defaults to "false". If "true" keyStorePasswordURL and
+ *      privateKeyPasswordURL must not be specified.</dd>
+ *
+ * </dl>
+ */
+public class KeyStoreLoginModule implements LoginModule {
+
+   static final java.util.ResourceBundle rb =
+        java.util.ResourceBundle.getBundle("sun.security.util.AuthResources");
+
+    /* -- Fields -- */
+
+    private static final int UNINITIALIZED = 0;
+    private static final int INITIALIZED = 1;
+    private static final int AUTHENTICATED = 2;
+    private static final int LOGGED_IN = 3;
+
+    private static final int PROTECTED_PATH = 0;
+    private static final int TOKEN = 1;
+    private static final int NORMAL = 2;
+
+    private static final String NONE = "NONE";
+    private static final String P11KEYSTORE = "PKCS11";
+
+    private static final TextOutputCallback bannerCallback =
+                new TextOutputCallback
+                        (TextOutputCallback.INFORMATION,
+                        rb.getString("Please enter keystore information"));
+    private final ConfirmationCallback confirmationCallback =
+                new ConfirmationCallback
+                        (ConfirmationCallback.INFORMATION,
+                        ConfirmationCallback.OK_CANCEL_OPTION,
+                        ConfirmationCallback.OK);
+
+    private Subject subject;
+    private CallbackHandler callbackHandler;
+    private Map sharedState;
+    private Map<String, ?> options;
+
+    private char[] keyStorePassword;
+    private char[] privateKeyPassword;
+    private KeyStore keyStore;
+
+    private String keyStoreURL;
+    private String keyStoreType;
+    private String keyStoreProvider;
+    private String keyStoreAlias;
+    private String keyStorePasswordURL;
+    private String privateKeyPasswordURL;
+    private boolean debug;
+    private javax.security.auth.x500.X500Principal principal;
+    private Certificate[] fromKeyStore;
+    private java.security.cert.CertPath certP = null;
+    private X500PrivateCredential privateCredential;
+    private int status = UNINITIALIZED;
+    private boolean nullStream = false;
+    private boolean token = false;
+    private boolean protectedPath = false;
+
+    /* -- Methods -- */
+
+    /**
+     * Initialize this <code>LoginModule</code>.
+     *
+     * <p>
+     *
+     * @param subject the <code>Subject</code> to be authenticated. <p>
+     *
+     * @param callbackHandler a <code>CallbackHandler</code> for communicating
+     *                  with the end user (prompting for usernames and
+     *                  passwords, for example),
+     *                  which may be <code>null</code>. <p>
+     *
+     * @param sharedState shared <code>LoginModule</code> state. <p>
+     *
+     * @param options options specified in the login
+     *                  <code>Configuration</code> for this particular
+     *                  <code>LoginModule</code>.
+     */
+
+    public void initialize(Subject subject,
+                           CallbackHandler callbackHandler,
+                           Map<String,?> sharedState,
+                           Map<String,?> options)
+    {
+        this.subject = subject;
+        this.callbackHandler = callbackHandler;
+        this.sharedState = sharedState;
+        this.options = options;
+
+        processOptions();
+        status = INITIALIZED;
+    }
+
+    private void processOptions() {
+        keyStoreURL = (String) options.get("keyStoreURL");
+        if (keyStoreURL == null) {
+            keyStoreURL =
+                "file:" +
+                System.getProperty("user.home").replace(
+                    File.separatorChar, '/') +
+                '/' + ".keystore";
+        } else if (NONE.equals(keyStoreURL)) {
+            nullStream = true;
+        }
+        keyStoreType = (String) options.get("keyStoreType");
+        if (keyStoreType == null) {
+            keyStoreType = KeyStore.getDefaultType();
+        }
+        if (P11KEYSTORE.equalsIgnoreCase(keyStoreType)) {
+            token = true;
+        }
+
+        keyStoreProvider = (String) options.get("keyStoreProvider");
+
+        keyStoreAlias = (String) options.get("keyStoreAlias");
+
+        keyStorePasswordURL = (String) options.get("keyStorePasswordURL");
+
+        privateKeyPasswordURL = (String) options.get("privateKeyPasswordURL");
+
+        protectedPath = "true".equalsIgnoreCase((String)options.get
+                                        ("protected"));
+
+        debug = "true".equalsIgnoreCase((String) options.get("debug"));
+        if (debug) {
+            debugPrint(null);
+            debugPrint("keyStoreURL=" + keyStoreURL);
+            debugPrint("keyStoreType=" + keyStoreType);
+            debugPrint("keyStoreProvider=" + keyStoreProvider);
+            debugPrint("keyStoreAlias=" + keyStoreAlias);
+            debugPrint("keyStorePasswordURL=" + keyStorePasswordURL);
+            debugPrint("privateKeyPasswordURL=" + privateKeyPasswordURL);
+            debugPrint("protectedPath=" + protectedPath);
+            debugPrint(null);
+        }
+    }
+
+    /**
+     * Authenticate the user.
+     *
+     * <p> Get the Keystore alias and relevant passwords.
+     * Retrieve the alias's principal and credentials from the Keystore.
+     *
+     * <p>
+     *
+     * @exception FailedLoginException if the authentication fails. <p>
+     *
+     * @return true in all cases (this <code>LoginModule</code>
+     *          should not be ignored).
+     */
+
+    public boolean login() throws LoginException {
+        switch (status) {
+        case UNINITIALIZED:
+        default:
+            throw new LoginException("The login module is not initialized");
+        case INITIALIZED:
+        case AUTHENTICATED:
+
+            if (token && !nullStream) {
+                throw new LoginException
+                        ("if keyStoreType is " + P11KEYSTORE +
+                        " then keyStoreURL must be " + NONE);
+            }
+
+            if (token && privateKeyPasswordURL != null) {
+                throw new LoginException
+                        ("if keyStoreType is " + P11KEYSTORE +
+                        " then privateKeyPasswordURL must not be specified");
+            }
+
+            if (protectedPath &&
+                (keyStorePasswordURL != null ||
+                        privateKeyPasswordURL != null)) {
+                throw new LoginException
+                        ("if protected is true then keyStorePasswordURL and " +
+                        "privateKeyPasswordURL must not be specified");
+            }
+
+            // get relevant alias and password info
+
+            if (protectedPath) {
+                getAliasAndPasswords(PROTECTED_PATH);
+            } else if (token) {
+                getAliasAndPasswords(TOKEN);
+            } else {
+                getAliasAndPasswords(NORMAL);
+            }
+
+            // log into KeyStore to retrieve data,
+            // then clear passwords
+
+            try {
+                getKeyStoreInfo();
+            } finally {
+                if (privateKeyPassword != null &&
+                    privateKeyPassword != keyStorePassword) {
+                    Arrays.fill(privateKeyPassword, '\0');
+                    privateKeyPassword = null;
+                }
+                if (keyStorePassword != null) {
+                    Arrays.fill(keyStorePassword, '\0');
+                    keyStorePassword = null;
+                }
+            }
+            status = AUTHENTICATED;
+            return true;
+        case LOGGED_IN:
+            return true;
+        }
+    }
+
+    /** Get the alias and passwords to use for looking up in the KeyStore. */
+    private void getAliasAndPasswords(int env) throws LoginException {
+        if (callbackHandler == null) {
+
+            // No callback handler - check for alias and password options
+
+            switch (env) {
+            case PROTECTED_PATH:
+                checkAlias();
+                break;
+            case TOKEN:
+                checkAlias();
+                checkStorePass();
+                break;
+            case NORMAL:
+                checkAlias();
+                checkStorePass();
+                checkKeyPass();
+                break;
+            }
+
+        } else {
+
+            // Callback handler available - prompt for alias and passwords
+
+            NameCallback aliasCallback;
+            if (keyStoreAlias == null || keyStoreAlias.length() == 0) {
+                aliasCallback = new NameCallback(
+                                        rb.getString("Keystore alias: "));
+            } else {
+                aliasCallback =
+                    new NameCallback(rb.getString("Keystore alias: "),
+                                     keyStoreAlias);
+            }
+
+            PasswordCallback storePassCallback = null;
+            PasswordCallback keyPassCallback = null;
+
+            switch (env) {
+            case PROTECTED_PATH:
+                break;
+            case NORMAL:
+                keyPassCallback = new PasswordCallback
+                    (rb.getString("Private key password (optional): "), false);
+                // fall thru
+            case TOKEN:
+                storePassCallback = new PasswordCallback
+                    (rb.getString("Keystore password: "), false);
+                break;
+            }
+            prompt(aliasCallback, storePassCallback, keyPassCallback);
+        }
+
+        if (debug) {
+            debugPrint("alias=" + keyStoreAlias);
+        }
+    }
+
+    private void checkAlias() throws LoginException {
+        if (keyStoreAlias == null) {
+            throw new LoginException
+                ("Need to specify an alias option to use " +
+                "KeyStoreLoginModule non-interactively.");
+        }
+    }
+
+    private void checkStorePass() throws LoginException {
+        if (keyStorePasswordURL == null) {
+            throw new LoginException
+                ("Need to specify keyStorePasswordURL option to use " +
+                "KeyStoreLoginModule non-interactively.");
+        }
+        InputStream in = null;
+        try {
+            in = new URL(keyStorePasswordURL).openStream();
+            keyStorePassword = Password.readPassword(in);
+        } catch (IOException e) {
+            LoginException le = new LoginException
+                ("Problem accessing keystore password \"" +
+                keyStorePasswordURL + "\"");
+            le.initCause(e);
+            throw le;
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException ioe) {
+                    LoginException le = new LoginException(
+                        "Problem closing the keystore password stream");
+                    le.initCause(ioe);
+                    throw le;
+                }
+            }
+        }
+    }
+
+    private void checkKeyPass() throws LoginException {
+        if (privateKeyPasswordURL == null) {
+            privateKeyPassword = keyStorePassword;
+        } else {
+            InputStream in = null;
+            try {
+                in = new URL(privateKeyPasswordURL).openStream();
+                privateKeyPassword = Password.readPassword(in);
+            } catch (IOException e) {
+                LoginException le = new LoginException
+                        ("Problem accessing private key password \"" +
+                        privateKeyPasswordURL + "\"");
+                le.initCause(e);
+                throw le;
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (IOException ioe) {
+                        LoginException le = new LoginException(
+                            "Problem closing the private key password stream");
+                        le.initCause(ioe);
+                        throw le;
+                    }
+                }
+            }
+        }
+    }
+
+    private void prompt(NameCallback aliasCallback,
+                        PasswordCallback storePassCallback,
+                        PasswordCallback keyPassCallback)
+                throws LoginException {
+
+        if (storePassCallback == null) {
+
+            // only prompt for alias
+
+            try {
+                callbackHandler.handle(
+                    new Callback[] {
+                        bannerCallback, aliasCallback, confirmationCallback
+                    });
+            } catch (IOException e) {
+                LoginException le = new LoginException
+                        ("Problem retrieving keystore alias");
+                le.initCause(e);
+                throw le;
+            } catch (UnsupportedCallbackException e) {
+                throw new LoginException(
+                    "Error: " + e.getCallback().toString() +
+                    " is not available to retrieve authentication " +
+                    " information from the user");
+            }
+
+            int confirmationResult = confirmationCallback.getSelectedIndex();
+
+            if (confirmationResult == ConfirmationCallback.CANCEL) {
+                throw new LoginException("Login cancelled");
+            }
+
+            saveAlias(aliasCallback);
+
+        } else if (keyPassCallback == null) {
+
+            // prompt for alias and key store password
+
+            try {
+                callbackHandler.handle(
+                    new Callback[] {
+                        bannerCallback, aliasCallback,
+                        storePassCallback, confirmationCallback
+                    });
+            } catch (IOException e) {
+                LoginException le = new LoginException
+                        ("Problem retrieving keystore alias and password");
+                le.initCause(e);
+                throw le;
+            } catch (UnsupportedCallbackException e) {
+                throw new LoginException(
+                    "Error: " + e.getCallback().toString() +
+                    " is not available to retrieve authentication " +
+                    " information from the user");
+            }
+
+            int confirmationResult = confirmationCallback.getSelectedIndex();
+
+            if (confirmationResult == ConfirmationCallback.CANCEL) {
+                throw new LoginException("Login cancelled");
+            }
+
+            saveAlias(aliasCallback);
+            saveStorePass(storePassCallback);
+
+        } else {
+
+            // prompt for alias, key store password, and key password
+
+            try {
+                callbackHandler.handle(
+                    new Callback[] {
+                        bannerCallback, aliasCallback,
+                        storePassCallback, keyPassCallback,
+                        confirmationCallback
+                    });
+            } catch (IOException e) {
+                LoginException le = new LoginException
+                        ("Problem retrieving keystore alias and passwords");
+                le.initCause(e);
+                throw le;
+            } catch (UnsupportedCallbackException e) {
+                throw new LoginException(
+                    "Error: " + e.getCallback().toString() +
+                    " is not available to retrieve authentication " +
+                    " information from the user");
+            }
+
+            int confirmationResult = confirmationCallback.getSelectedIndex();
+
+            if (confirmationResult == ConfirmationCallback.CANCEL) {
+                throw new LoginException("Login cancelled");
+            }
+
+            saveAlias(aliasCallback);
+            saveStorePass(storePassCallback);
+            saveKeyPass(keyPassCallback);
+        }
+    }
+
+    private void saveAlias(NameCallback cb) {
+        keyStoreAlias = cb.getName();
+    }
+
+    private void saveStorePass(PasswordCallback c) {
+        keyStorePassword = c.getPassword();
+        if (keyStorePassword == null) {
+            /* Treat a NULL password as an empty password */
+            keyStorePassword = new char[0];
+        }
+        c.clearPassword();
+    }
+
+    private void saveKeyPass(PasswordCallback c) {
+        privateKeyPassword = c.getPassword();
+        if (privateKeyPassword == null || privateKeyPassword.length == 0) {
+            /*
+             * Use keystore password if no private key password is
+             * specified.
+             */
+            privateKeyPassword = keyStorePassword;
+        }
+        c.clearPassword();
+    }
+
+    /** Get the credentials from the KeyStore. */
+    private void getKeyStoreInfo() throws LoginException {
+
+        /* Get KeyStore instance */
+        try {
+            if (keyStoreProvider == null) {
+                keyStore = KeyStore.getInstance(keyStoreType);
+            } else {
+                keyStore =
+                    KeyStore.getInstance(keyStoreType, keyStoreProvider);
+            }
+        } catch (KeyStoreException e) {
+            LoginException le = new LoginException
+                ("The specified keystore type was not available");
+            le.initCause(e);
+            throw le;
+        } catch (NoSuchProviderException e) {
+            LoginException le = new LoginException
+                ("The specified keystore provider was not available");
+            le.initCause(e);
+            throw le;
+        }
+
+        /* Load KeyStore contents from file */
+        InputStream in = null;
+        try {
+            if (nullStream) {
+                // if using protected auth path, keyStorePassword will be null
+                keyStore.load(null, keyStorePassword);
+            } else {
+                in = new URL(keyStoreURL).openStream();
+                keyStore.load(in, keyStorePassword);
+            }
+        } catch (MalformedURLException e) {
+            LoginException le = new LoginException
+                                ("Incorrect keyStoreURL option");
+            le.initCause(e);
+            throw le;
+        } catch (GeneralSecurityException e) {
+            LoginException le = new LoginException
+                                ("Error initializing keystore");
+            le.initCause(e);
+            throw le;
+        } catch (IOException e) {
+            LoginException le = new LoginException
+                                ("Error initializing keystore");
+            le.initCause(e);
+            throw le;
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException ioe) {
+                    LoginException le = new LoginException
+                                ("Error initializing keystore");
+                    le.initCause(ioe);
+                    throw le;
+                }
+            }
+        }
+
+        /* Get certificate chain and create a certificate path */
+        try {
+            fromKeyStore =
+                keyStore.getCertificateChain(keyStoreAlias);
+            if (fromKeyStore == null
+                || fromKeyStore.length == 0
+                || !(fromKeyStore[0] instanceof X509Certificate))
+            {
+                throw new FailedLoginException(
+                    "Unable to find X.509 certificate chain in keystore");
+            } else {
+                LinkedList<Certificate> certList =
+                                new LinkedList<Certificate>();
+                for (int i=0; i < fromKeyStore.length; i++) {
+                    certList.add(fromKeyStore[i]);
+                }
+                CertificateFactory certF=
+                    CertificateFactory.getInstance("X.509");
+                certP =
+                    certF.generateCertPath(certList);
+            }
+        } catch (KeyStoreException e) {
+            LoginException le = new LoginException("Error using keystore");
+            le.initCause(e);
+            throw le;
+        } catch (CertificateException ce) {
+            LoginException le = new LoginException
+                ("Error: X.509 Certificate type unavailable");
+            le.initCause(ce);
+            throw le;
+        }
+
+        /* Get principal and keys */
+        try {
+            X509Certificate certificate = (X509Certificate)fromKeyStore[0];
+            principal = new javax.security.auth.x500.X500Principal
+                (certificate.getSubjectDN().getName());
+
+            // if token, privateKeyPassword will be null
+            Key privateKey = keyStore.getKey(keyStoreAlias, privateKeyPassword);
+            if (privateKey == null
+                || !(privateKey instanceof PrivateKey))
+            {
+                throw new FailedLoginException(
+                    "Unable to recover key from keystore");
+            }
+
+            privateCredential = new X500PrivateCredential(
+                certificate, (PrivateKey) privateKey, keyStoreAlias);
+        } catch (KeyStoreException e) {
+            LoginException le = new LoginException("Error using keystore");
+            le.initCause(e);
+            throw le;
+        } catch (NoSuchAlgorithmException e) {
+            LoginException le = new LoginException("Error using keystore");
+            le.initCause(e);
+            throw le;
+        } catch (UnrecoverableKeyException e) {
+            FailedLoginException fle = new FailedLoginException
+                                ("Unable to recover key from keystore");
+            fle.initCause(e);
+            throw fle;
+        }
+        if (debug) {
+            debugPrint("principal=" + principal +
+                       "\n certificate="
+                       + privateCredential.getCertificate() +
+                       "\n alias =" + privateCredential.getAlias());
+        }
+    }
+
+    /**
+     * Abstract method to commit the authentication process (phase 2).
+     *
+     * <p> This method is called if the LoginContext's
+     * overall authentication succeeded
+     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
+     * succeeded).
+     *
+     * <p> If this LoginModule's own authentication attempt
+     * succeeded (checked by retrieving the private state saved by the
+     * <code>login</code> method), then this method associates a
+     * <code>X500Principal</code> for the subject distinguished name of the
+     * first certificate in the alias's credentials in the subject's
+     * principals,the alias's certificate path in the subject's public
+     * credentials, and a<code>X500PrivateCredential</code> whose certificate
+     * is the first  certificate in the alias's certificate path and whose
+     * private key is the alias's private key in the subject's private
+     * credentials.  If this LoginModule's own
+     * authentication attempted failed, then this method removes
+     * any state that was originally saved.
+     *
+     * <p>
+     *
+     * @exception LoginException if the commit fails
+     *
+     * @return true if this LoginModule's own login and commit
+     *          attempts succeeded, or false otherwise.
+     */
+
+    public boolean commit() throws LoginException {
+        switch (status) {
+        case UNINITIALIZED:
+        default:
+            throw new LoginException("The login module is not initialized");
+        case INITIALIZED:
+            logoutInternal();
+            throw new LoginException("Authentication failed");
+        case AUTHENTICATED:
+            if (commitInternal()) {
+                return true;
+            } else {
+                logoutInternal();
+                throw new LoginException("Unable to retrieve certificates");
+            }
+        case LOGGED_IN:
+            return true;
+        }
+    }
+
+    private boolean commitInternal() throws LoginException {
+        /* If the subject is not readonly add to the principal and credentials
+         * set; otherwise just return true
+         */
+        if (subject.isReadOnly()) {
+            throw new LoginException ("Subject is set readonly");
+        } else {
+            subject.getPrincipals().add(principal);
+            subject.getPublicCredentials().add(certP);
+            subject.getPrivateCredentials().add(privateCredential);
+            status = LOGGED_IN;
+            return true;
+        }
+    }
+
+    /**
+     * <p> This method is called if the LoginContext's
+     * overall authentication failed.
+     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
+     * did not succeed).
+     *
+     * <p> If this LoginModule's own authentication attempt
+     * succeeded (checked by retrieving the private state saved by the
+     * <code>login</code> and <code>commit</code> methods),
+     * then this method cleans up any state that was originally saved.
+     *
+     * <p> If the loaded KeyStore's provider extends
+     * <code>java.security.AuthProvider</code>,
+     * then the provider's <code>logout</code> method is invoked.
+     *
+     * <p>
+     *
+     * @exception LoginException if the abort fails.
+     *
+     * @return false if this LoginModule's own login and/or commit attempts
+     *          failed, and true otherwise.
+     */
+
+    public boolean abort() throws LoginException {
+        switch (status) {
+        case UNINITIALIZED:
+        default:
+            return false;
+        case INITIALIZED:
+            return false;
+        case AUTHENTICATED:
+            logoutInternal();
+            return true;
+        case LOGGED_IN:
+            logoutInternal();
+            return true;
+        }
+    }
+    /**
+     * Logout a user.
+     *
+     * <p> This method removes the Principals, public credentials and the
+     * private credentials that were added by the <code>commit</code> method.
+     *
+     * <p> If the loaded KeyStore's provider extends
+     * <code>java.security.AuthProvider</code>,
+     * then the provider's <code>logout</code> method is invoked.
+     *
+     * <p>
+     *
+     * @exception LoginException if the logout fails.
+     *
+     * @return true in all cases since this <code>LoginModule</code>
+     *          should not be ignored.
+     */
+
+    public boolean logout() throws LoginException {
+        if (debug)
+            debugPrint("Entering logout " + status);
+        switch (status) {
+        case UNINITIALIZED:
+            throw new LoginException
+                ("The login module is not initialized");
+        case INITIALIZED:
+        case AUTHENTICATED:
+        default:
+           // impossible for LoginModule to be in AUTHENTICATED
+           // state
+           // assert status != AUTHENTICATED;
+            return false;
+        case LOGGED_IN:
+            logoutInternal();
+            return true;
+        }
+    }
+
+    private void logoutInternal() throws LoginException {
+        if (debug) {
+            debugPrint("Entering logoutInternal");
+        }
+
+        // assumption is that KeyStore.load did a login -
+        // perform explicit logout if possible
+        LoginException logoutException = null;
+        Provider provider = keyStore.getProvider();
+        if (provider instanceof AuthProvider) {
+            AuthProvider ap = (AuthProvider)provider;
+            try {
+                ap.logout();
+                if (debug) {
+                    debugPrint("logged out of KeyStore AuthProvider");
+                }
+            } catch (LoginException le) {
+                // save but continue below
+                logoutException = le;
+            }
+        }
+
+        if (subject.isReadOnly()) {
+            // attempt to destroy the private credential
+            // even if the Subject is read-only
+            principal = null;
+            certP = null;
+            status = INITIALIZED;
+            // destroy the private credential
+            Iterator<Object> it = subject.getPrivateCredentials().iterator();
+            while (it.hasNext()) {
+                Object obj = it.next();
+                if (privateCredential.equals(obj)) {
+                    privateCredential = null;
+                    try {
+                        ((Destroyable)obj).destroy();
+                        if (debug)
+                            debugPrint("Destroyed private credential, " +
+                                       obj.getClass().getName());
+                        break;
+                    } catch (DestroyFailedException dfe) {
+                        LoginException le = new LoginException
+                            ("Unable to destroy private credential, "
+                             + obj.getClass().getName());
+                        le.initCause(dfe);
+                        throw le;
+                    }
+                }
+            }
+
+            // throw an exception because we can not remove
+            // the principal and public credential from this
+            // read-only Subject
+            throw new LoginException
+                ("Unable to remove Principal ("
+                 + "X500Principal "
+                 + ") and public credential (certificatepath) "
+                 + "from read-only Subject");
+        }
+        if (principal != null) {
+            subject.getPrincipals().remove(principal);
+            principal = null;
+        }
+        if (certP != null) {
+            subject.getPublicCredentials().remove(certP);
+            certP = null;
+        }
+        if (privateCredential != null) {
+            subject.getPrivateCredentials().remove(privateCredential);
+            privateCredential = null;
+        }
+
+        // throw pending logout exception if there is one
+        if (logoutException != null) {
+            throw logoutException;
+        }
+        status = INITIALIZED;
+    }
+
+    private void debugPrint(String message) {
+        // we should switch to logging API
+        if (message == null) {
+            System.err.println();
+        } else {
+            System.err.println("Debug KeyStoreLoginModule: " + message);
+        }
+    }
+}