| /* |
| * Copyright (c) 2000, 2011, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.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<>(); |
| 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); |
| } |
| } |
| } |