com.sun.enterprise.security.auth.login.ClientCertificateLoginModule Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.enterprise.security.auth.login;
import com.sun.enterprise.security.auth.login.common.X509CertificateCredential;
import java.util.Map;
import java.util.Enumeration;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
//V3:Commented import com.sun.enterprise.config.clientbeans.Ssl;
import com.sun.enterprise.security.ssl.SSLUtils;
import com.sun.enterprise.security.integration.AppClientSSL;
import org.glassfish.security.common.PrincipalImpl;
import com.sun.enterprise.util.LocalStringManagerImpl;
import java.util.logging.*;
import com.sun.logging.*;
import org.glassfish.internal.api.Globals;
/**
* This LoginModule authenticates users with X509 certificates.
*
*
If testUser successfully authenticates itself,
* a PrincipalImpl
with the testUser's username
* is added to the Subject.
*
*
This LoginModule recognizes the debug option.
* If set to true in the login Configuration,
* debug messages will be output to the output stream, System.out.
*
* @author Harpreet Singh ([email protected])
*/
public class ClientCertificateLoginModule implements LoginModule {
private static Logger _logger=null;
static {
_logger=LogDomains.getLogger(ClientCertificateLoginModule.class, LogDomains.SECURITY_LOGGER);
}
private static LocalStringManagerImpl localStrings =
new LocalStringManagerImpl(ClientCertificateLoginModule.class);
private static KeyStore ks = null;
// initial state
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
// configurable option
private boolean debug = false; // default
// the authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
private String alias;
private X509Certificate certificate;
// testUser's PrincipalImpl
private PrincipalImpl userPrincipal;
private AppClientSSL ssl;
private SSLUtils sslUtils;
/**
* 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).
*
* @param sharedState shared LoginModule
state.
*
* @param options options specified in the login
* Configuration
for this particular
* LoginModule
.
*/
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
// initialize any configured options
debug = "true".equalsIgnoreCase((String)options.get("debug"));
sslUtils = Globals.getDefaultHabitat().getComponent(SSLUtils.class);
}
/**
* Authenticate the user by prompting for a username and password.
*
*
*
* @return true in all cases since this LoginModule
* should not be ignored.
*
* @exception FailedLoginException if the authentication fails.
*
* @exception LoginException if this LoginModule
* is unable to perform the authentication.
*/
public boolean login() throws LoginException {
// prompt for a username and password
if (callbackHandler == null){
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the user");
}
try {
String[] as = new String[ks.size()];
String[] aliasString = new String[ks.size()];
Enumeration aliases = ks.aliases();
for(int i = 0; i < ks.size(); i++) {
aliasString[i] = (String) aliases.nextElement();
as[i] = ((X509Certificate)ks.getCertificate(aliasString[i])).getSubjectDN().getName();
}
Callback[] callbacks = new Callback[1];
callbacks[0] = new ChoiceCallback(localStrings.getLocalString("login.certificate", "Choose from list of certificates: "), as, 0, false);
callbackHandler.handle(callbacks);
String[] choices = ((ChoiceCallback)callbacks[0]).getChoices();
int[] idx = ((ChoiceCallback)callbacks[0]).getSelectedIndexes();
if (choices == null) {
// figure out
}
if (idx == null) {
throw new LoginException ("No certificate selected!");
} else if (idx[0] == -1){
throw new LoginException ("Incorrect keystore password");
}
// print debugging information
if (debug) {
if(_logger.isLoggable(Level.FINE)){
_logger.log(Level.FINE,"\t\t[ClientCertificateLoginModule] " +
"user entered certificate: ");
for (int i = 0; i < idx.length; i++){
_logger.log(Level.FINE,aliasString[idx[i]]);
}
}
}
// the authenticate method previously picked out the
// wrong alias.
// since we allow only 1 choice the first element in idx
// idx[0] should have the selected index.
this.alias = aliasString[idx[0]];
certificate = (X509Certificate) ks.getCertificate(alias);
// the authenticate should always return a true.
if (debug){
if(_logger.isLoggable(Level.FINE)){
_logger.log(Level.FINE,"\t\t[ClientCertificateLoginModule] " +
"authentication succeeded");
}
}
succeeded = true;
return true;
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString() +
" not available to garner authentication information " +
"from the user");
} catch (Exception e) {
throw new LoginException(e.toString());
}
}
/**
*
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
* PrincipalImpl
* with the Subject
located in the
* LoginModule
. 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.
*/
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
} else {
// add a Principal (authenticated identity)
// to the Subject
// assume the user we authenticated is the PrincipalImpl
userPrincipal = new PrincipalImpl(alias);
if (!subject.getPrincipals().contains(userPrincipal)){
subject.getPrincipals().add(userPrincipal);
}
if (debug) {
if(_logger.isLoggable(Level.FINE)){
_logger.log(Level.FINE,"\t\t[ClientCertificateLoginModule] " +
"added PrincipalImpl to Subject");
}
}
ssl = new AppClientSSL();
ssl.setCertNickname(this.alias);
sslUtils.setAppclientSsl(ssl);
String realm = LoginContextDriver.CERT_REALMNAME;
X509Certificate[] certChain = new X509Certificate[1];
certChain[0] = certificate;
X509CertificateCredential pc =
new X509CertificateCredential(certChain, alias, realm);
if(!subject.getPrivateCredentials().contains(pc)) {
subject.getPrivateCredentials().add(pc);
}
commitSucceeded = true;
return true;
}
}
/**
*
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.
*
*
*
* @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 {
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// login succeeded but overall authentication failed
succeeded = false;
alias = null;
userPrincipal = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Logout the user.
*
*
This method removes the PrincipalImpl
* that was added by the commit
method.
*
*
*
* @exception LoginException if the logout fails.
*
* @return true in all cases since this LoginModule
* should not be ignored.
*/
public boolean logout() throws LoginException {
// unset the alias
ssl = null;
sslUtils.setAppclientSsl(ssl);
subject.getPrincipals().remove(userPrincipal);
succeeded = false;
succeeded = commitSucceeded;
alias = null;
userPrincipal = null;
return true;
}
public static void setKeyStore(KeyStore keyStore) {
ks = keyStore;
}
}