940 lines
34 KiB
Java
940 lines
34 KiB
Java
/*
|
|
* Copyright (c) 2000, 2013, 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 java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.security.*;
|
|
import java.security.cert.*;
|
|
import java.security.cert.Certificate;
|
|
import java.security.cert.X509Certificate;
|
|
import java.util.*;
|
|
import javax.security.auth.Destroyable;
|
|
import javax.security.auth.DestroyFailedException;
|
|
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.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>
|
|
*/
|
|
@jdk.Exported
|
|
public class KeyStoreLoginModule implements LoginModule {
|
|
|
|
private static final ResourceBundle rb = AccessController.doPrivileged(
|
|
new PrivilegedAction<ResourceBundle>() {
|
|
public ResourceBundle run() {
|
|
return 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<String, Object> 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>.
|
|
*/
|
|
// Unchecked warning from (Map<String, Object>)sharedState is safe
|
|
// since javax.security.auth.login.LoginContext passes a raw HashMap.
|
|
@SuppressWarnings("unchecked")
|
|
public void initialize(Subject subject,
|
|
CallbackHandler callbackHandler,
|
|
Map<String,?> sharedState,
|
|
Map<String,?> options)
|
|
{
|
|
this.subject = subject;
|
|
this.callbackHandler = callbackHandler;
|
|
this.sharedState = (Map<String, Object>)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. */
|
|
@SuppressWarnings("fallthrough")
|
|
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);
|
|
}
|
|
}
|
|
}
|