FileDocCategorySizeDatePackage
KeyStoreLoginModule.javaAPI DocJava SE 5 API27237Fri Aug 26 14:56:14 BST 2005com.sun.security.auth.module

KeyStoreLoginModule

public class KeyStoreLoginModule extends Object implements LoginModule
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 X500Principal 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 X500PrivateCredential 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.

Recognizes the following options in the configuration file:

keyStoreURL
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 user.home system property. The input stream from this URL is passed to the KeyStore.load method. "NONE" may be specified if a null stream must be passed to the KeyStore.load method. "NONE" should be specified if the KeyStore resides on a hardware token device, for example.
keyStoreType
The key store type. If not specified, defaults to the result of calling KeyStore.getDefaultType(). If the type is "PKCS11", then keyStoreURL must be "NONE" and privateKeyPasswordURL must not be specified.
keyStoreProvider
The key store provider. If not specified, uses the standard search order to find the provider.
keyStoreAlias
The alias in the key store to login as. Required when no callback handler is provided. No default value.
keyStorePasswordURL
A URL that specifies the location of the key store password. Required when no callback handler is provided and protected is false. No default value.
privateKeyPasswordURL
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.
protected
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.

Fields Summary
static final ResourceBundle
rb
private static final int
UNINITIALIZED
private static final int
INITIALIZED
private static final int
AUTHENTICATED
private static final int
LOGGED_IN
private static final int
PROTECTED_PATH
private static final int
TOKEN
private static final int
NORMAL
private static final String
NONE
private static final String
P11KEYSTORE
private static final TextOutputCallback
bannerCallback
private final ConfirmationCallback
confirmationCallback
private Subject
subject
private CallbackHandler
callbackHandler
private Map
sharedState
private Map
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 X500Principal
principal
private Certificate[]
fromKeyStore
private CertPath
certP
private X500PrivateCredential
privateCredential
private int
status
private boolean
nullStream
private boolean
token
private boolean
protectedPath
Constructors Summary
Methods Summary
public booleanabort()

This method is called if the LoginContext's overall authentication failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules did not succeed).

If this LoginModule's own authentication attempt succeeded (checked by retrieving the private state saved by the login and commit methods), then this method cleans up any state that was originally saved.

If the loaded KeyStore's provider extends java.security.AuthProvider, then the provider's logout method is invoked.

exception
LoginException if the abort fails.
return
false if this LoginModule's own login and/or commit attempts failed, and true otherwise.

	switch (status) {
	case UNINITIALIZED:
	default:
	    return false;
	case INITIALIZED:
	    return false;
	case AUTHENTICATED:
	    logoutInternal();
	    return true;
	case LOGGED_IN:
	    logoutInternal();
	    return true;
	}
    
private voidcheckAlias()

	if (keyStoreAlias == null) {
	    throw new LoginException
		("Need to specify an alias option to use " +
		"KeyStoreLoginModule non-interactively.");
	}
    
private voidcheckKeyPass()

	if (privateKeyPasswordURL == null) {
	    privateKeyPassword = keyStorePassword;
	} else {
	    try {
		InputStream in = new URL(privateKeyPasswordURL).openStream();
		privateKeyPassword = Password.readPassword(in);
		in.close();
	    } catch (IOException e) {
		LoginException le = new LoginException
			("Problem accessing private key password \"" +
			privateKeyPasswordURL + "\"");
		le.initCause(e);
		throw le;
	    }
	}
    
private voidcheckStorePass()

	if (keyStorePasswordURL == null) {
	    throw new LoginException
		("Need to specify keyStorePasswordURL option to use " +
		"KeyStoreLoginModule non-interactively.");
	}
	try {
	    InputStream in = new URL(keyStorePasswordURL).openStream();
	    keyStorePassword = Password.readPassword(in);
	    in.close();
	} catch (IOException e) {
	    LoginException le = new LoginException
		("Problem accessing keystore password \"" +
		keyStorePasswordURL + "\"");
	    le.initCause(e);
	    throw le;
	}
    
public booleancommit()
Abstract method to commit the authentication process (phase 2).

This method is called if the LoginContext's overall authentication succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules succeeded).

If this LoginModule's own authentication attempt succeeded (checked by retrieving the private state saved by the login method), then this method associates a X500Principal 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 aX500PrivateCredential 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.

exception
LoginException if the commit fails
return
true if this LoginModule's own login and commit attempts succeeded, or false otherwise.

	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 booleancommitInternal()

	/* 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;
	}
    
private voiddebugPrint(java.lang.String message)

	// we should switch to logging API
	if (message == null) {
	    System.err.println();
	} else {
	    System.err.println("Debug KeyStoreLoginModule: " + message);
	}
    
private voidgetAliasAndPasswords(int env)
Get the alias and passwords to use for looking up in the KeyStore.

	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 voidgetKeyStoreInfo()
Get the credentials from the KeyStore.


	/* 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 */
	try {
	    if (nullStream) {
		// if using protected auth path, keyStorePassword will be null
		keyStore.load(null, keyStorePassword);
	    } else {
		InputStream in = new URL(keyStoreURL).openStream();
		keyStore.load(in, keyStorePassword);
		in.close();
	    }
	} 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;
	}

	/* 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 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());
	}
    
public voidinitialize(javax.security.auth.Subject subject, javax.security.auth.callback.CallbackHandler callbackHandler, java.util.Map sharedState, java.util.Map options)
Initialize this LoginModule.

param
subject the Subject to be authenticated.

param
callbackHandler a CallbackHandler for communicating with the end user (prompting for usernames and passwords, for example), which may be null.

param
sharedState shared LoginModule state.

param
options options specified in the login Configuration for this particular LoginModule.

 

    /* -- Methods -- */
  
                      			       			  			                 			   			     

       
			    
			    
			    
    
 	this.subject = subject;
	this.callbackHandler = callbackHandler;
	this.sharedState = sharedState;
	this.options = options;

	processOptions();
	status = INITIALIZED;
    
public booleanlogin()
Authenticate the user.

Get the Keystore alias and relevant passwords. Retrieve the alias's principal and credentials from the Keystore.

exception
FailedLoginException if the authentication fails.

return
true in all cases (this LoginModule should not be ignored).

	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;
	}
    
public booleanlogout()
Logout a user.

This method removes the Principals, public credentials and the private credentials that were added by the commit method.

If the loaded KeyStore's provider extends java.security.AuthProvider, then the provider's logout method is invoked.

exception
LoginException if the logout fails.
return
true in all cases since this LoginModule should not be ignored.

	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 voidlogoutInternal()

	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 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 voidprocessOptions()

	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);
	}
    
private voidprompt(javax.security.auth.callback.NameCallback aliasCallback, javax.security.auth.callback.PasswordCallback storePassCallback, javax.security.auth.callback.PasswordCallback keyPassCallback)


	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 voidsaveAlias(javax.security.auth.callback.NameCallback cb)

	keyStoreAlias = cb.getName();
    
private voidsaveKeyPass(javax.security.auth.callback.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();
    
private voidsaveStorePass(javax.security.auth.callback.PasswordCallback c)

	keyStorePassword = c.getPassword();
	if (keyStorePassword == null) {
	    /* Treat a NULL password as an empty password */
	    keyStorePassword = new char[0];
	}
	c.clearPassword();