/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* 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.
*/
/**
* 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>
*/
/* -- Fields -- */
private char[] keyStorePassword;
private char[] privateKeyPassword;
private boolean debug;
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>.
*/
{
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
}
private void processOptions() {
if (keyStoreURL == null) {
"file:" +
'/' + ".keystore";
nullStream = true;
}
if (keyStoreType == null) {
}
token = true;
}
("protected"));
if (debug) {
}
}
/**
* 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).
*/
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);
}
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) {
} else if (token) {
} else {
}
// log into KeyStore to retrieve data,
// then clear passwords
try {
} finally {
if (privateKeyPassword != null &&
}
if (keyStorePassword != null) {
}
}
return true;
case LOGGED_IN:
return true;
}
}
/** 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();
break;
case NORMAL:
checkAlias();
checkKeyPass();
break;
}
} else {
// Callback handler available - prompt for alias and passwords
aliasCallback = new NameCallback(
} else {
}
switch (env) {
case PROTECTED_PATH:
break;
case NORMAL:
// fall thru
case TOKEN:
break;
}
}
if (debug) {
}
}
if (keyStoreAlias == null) {
throw new LoginException
("Need to specify an alias option to use " +
"KeyStoreLoginModule non-interactively.");
}
}
if (keyStorePasswordURL == null) {
throw new LoginException
("Need to specify keyStorePasswordURL option to use " +
"KeyStoreLoginModule non-interactively.");
}
try {
} catch (IOException e) {
("Problem accessing keystore password \"" +
keyStorePasswordURL + "\"");
throw le;
} finally {
try {
} catch (IOException ioe) {
"Problem closing the keystore password stream");
throw le;
}
}
}
}
if (privateKeyPasswordURL == null) {
} else {
try {
} catch (IOException e) {
("Problem accessing private key password \"" +
privateKeyPasswordURL + "\"");
throw le;
} finally {
try {
} catch (IOException ioe) {
"Problem closing the private key password stream");
throw le;
}
}
}
}
}
throws LoginException {
if (storePassCallback == null) {
// only prompt for alias
try {
new Callback[] {
});
} catch (IOException e) {
("Problem retrieving keystore alias");
throw le;
} catch (UnsupportedCallbackException e) {
throw new LoginException(
" is not available to retrieve authentication " +
" information from the user");
}
throw new LoginException("Login cancelled");
}
} else if (keyPassCallback == null) {
// prompt for alias and key store password
try {
new Callback[] {
});
} catch (IOException e) {
("Problem retrieving keystore alias and password");
throw le;
} catch (UnsupportedCallbackException e) {
throw new LoginException(
" is not available to retrieve authentication " +
" information from the user");
}
throw new LoginException("Login cancelled");
}
} else {
// prompt for alias, key store password, and key password
try {
new Callback[] {
});
} catch (IOException e) {
("Problem retrieving keystore alias and passwords");
throw le;
} catch (UnsupportedCallbackException e) {
throw new LoginException(
" is not available to retrieve authentication " +
" information from the user");
}
throw new LoginException("Login cancelled");
}
}
}
}
keyStorePassword = c.getPassword();
if (keyStorePassword == null) {
/* Treat a NULL password as an empty password */
keyStorePassword = new char[0];
}
c.clearPassword();
}
privateKeyPassword = c.getPassword();
/*
* Use keystore password if no private key password is
* specified.
*/
}
c.clearPassword();
}
/** Get the credentials from the KeyStore. */
/* Get KeyStore instance */
try {
if (keyStoreProvider == null) {
} else {
keyStore =
}
} catch (KeyStoreException e) {
("The specified keystore type was not available");
throw le;
} catch (NoSuchProviderException e) {
("The specified keystore provider was not available");
throw le;
}
/* Load KeyStore contents from file */
try {
if (nullStream) {
// if using protected auth path, keyStorePassword will be null
} else {
}
} catch (MalformedURLException e) {
("Incorrect keyStoreURL option");
throw le;
} catch (GeneralSecurityException e) {
("Error initializing keystore");
throw le;
} catch (IOException e) {
("Error initializing keystore");
throw le;
} finally {
try {
} catch (IOException ioe) {
("Error initializing keystore");
throw le;
}
}
}
/* Get certificate chain and create a certificate path */
try {
if (fromKeyStore == null
{
throw new FailedLoginException(
"Unable to find X.509 certificate chain in keystore");
} else {
}
certP =
}
} catch (KeyStoreException e) {
throw le;
} catch (CertificateException ce) {
("Error: X.509 Certificate type unavailable");
throw le;
}
/* Get principal and keys */
try {
// if token, privateKeyPassword will be null
if (privateKey == null
|| !(privateKey instanceof PrivateKey))
{
throw new FailedLoginException(
"Unable to recover key from keystore");
}
} catch (KeyStoreException e) {
throw le;
} catch (NoSuchAlgorithmException e) {
throw le;
} catch (UnrecoverableKeyException e) {
("Unable to recover key from keystore");
throw fle;
}
if (debug) {
"\n certificate="
}
}
/**
* 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.
*/
switch (status) {
case UNINITIALIZED:
default:
throw new LoginException("The login module is not initialized");
case INITIALIZED:
throw new LoginException("Authentication failed");
case AUTHENTICATED:
if (commitInternal()) {
return true;
} else {
throw new LoginException("Unable to retrieve certificates");
}
case LOGGED_IN:
return true;
}
}
/* 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 {
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.
*
* failed, and true otherwise.
*/
switch (status) {
case UNINITIALIZED:
default:
return false;
case INITIALIZED:
return false;
case AUTHENTICATED:
return true;
case LOGGED_IN:
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.
*/
if (debug)
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:
return true;
}
}
if (debug) {
debugPrint("Entering logoutInternal");
}
// assumption is that KeyStore.load did a login -
// perform explicit logout if possible
if (provider instanceof AuthProvider) {
try {
if (debug) {
debugPrint("logged out of KeyStore AuthProvider");
}
} catch (LoginException le) {
// save but continue below
}
}
if (subject.isReadOnly()) {
// attempt to destroy the private credential
// even if the Subject is read-only
// destroy the private credential
try {
if (debug)
debugPrint("Destroyed private credential, " +
break;
} catch (DestroyFailedException dfe) {
("Unable to destroy private credential, "
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 (privateCredential != null) {
}
// throw pending logout exception if there is one
if (logoutException != null) {
throw logoutException;
}
}
// we should switch to logging API
} else {
}
}
}