All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.security.auth.login.ClientCertificateLoginModule Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2013 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.
 */
// Portions Copyright [2018-2019] [Payara Foundation and/or its affiliates]
package com.sun.enterprise.security.auth.login;

import static com.sun.enterprise.security.auth.login.LoginContextDriver.CERT_REALMNAME;
import static java.util.logging.Level.FINE;

import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.ChoiceCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.x500.X500Principal;

import com.sun.enterprise.security.auth.realm.certificate.OID;

import org.glassfish.internal.api.Globals;
import org.glassfish.security.common.PrincipalImpl;

import com.sun.enterprise.security.SecurityLoggerInfo;
import com.sun.enterprise.security.auth.login.common.X509CertificateCredential;
import com.sun.enterprise.security.integration.AppClientSSL;
import com.sun.enterprise.security.ssl.SSLUtils;
import com.sun.enterprise.util.LocalStringManagerImpl;

/**
 * 

* 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 final static Logger _logger = SecurityLoggerInfo.getLogger(); private final static LocalStringManagerImpl localStrings = new LocalStringManagerImpl(ClientCertificateLoginModule.class); private static KeyStore keyStore; // Initial state private Subject subject; private CallbackHandler callbackHandler; // Configurable option private boolean debug; // The authentication status private boolean succeeded; private boolean commitSucceeded; private String alias; private X509Certificate certificate; 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. (unused) *

* * @param options options specified in the login Configuration for this particular * LoginModule. Only used for the "debug" option. */ @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this.subject = subject; this.callbackHandler = callbackHandler; // Initialize any configured options debug = "true".equalsIgnoreCase((String) options.get("debug")); sslUtils = Globals.getDefaultHabitat().getService(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 LoginException if this LoginModule is unable to perform the * authentication. */ @Override 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[] certificateNames = new String[keyStore.size()]; String[] aliasNames = new String[keyStore.size()]; Enumeration aliases = keyStore.aliases(); for (int i = 0; i < keyStore.size(); i++) { aliasNames[i] = aliases.nextElement(); certificateNames[i] = ((X509Certificate) keyStore.getCertificate(aliasNames[i])) .getSubjectX500Principal().getName(X500Principal.RFC2253, OID.getOIDMap()); } Callback[] callbacks = new Callback[] {createChoiceCallback(certificateNames)}; callbackHandler.handle(callbacks); int[] selectedIndexes = ((ChoiceCallback) callbacks[0]).getSelectedIndexes(); if (selectedIndexes == null) { throw new LoginException("No certificate selected!"); } else if (selectedIndexes[0] == -1) { throw new LoginException("Incorrect keystore password"); } if (debug) { _logger.fine(() -> "[ClientCertificateLoginModule] user entered certificates: " + Arrays.stream(selectedIndexes).mapToObj(i -> aliasNames[i]).collect(Collectors.toList())); } // 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. alias = aliasNames[selectedIndexes[0]]; certificate = (X509Certificate) keyStore.getCertificate(alias); // The authenticate should always return a true. if (debug) { _logger.fine("\t\t[ClientCertificateLoginModule] authentication succeeded"); } succeeded = true; return true; } catch (UnsupportedCallbackException uce) { throw new LoginException( "Error: " + uce.getCallback() + " 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. */ @Override public boolean commit() throws LoginException { if (succeeded == false) { return false; } // 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(FINE)) { _logger.log(FINE, "\t\t[ClientCertificateLoginModule] " + "added PrincipalImpl to Subject"); } } ssl = new AppClientSSL(); ssl.setCertNickname(this.alias); sslUtils.setAppclientSsl(ssl); X509Certificate[] certChain = new X509Certificate[1]; certChain[0] = certificate; X509CertificateCredential pc = new X509CertificateCredential(certChain, alias, CERT_REALMNAME); 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. */ @Override public boolean abort() throws LoginException { if (succeeded == false) { return false; } 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. */ @Override public boolean logout() throws LoginException { // Unset the alias ssl = null; sslUtils.setAppclientSsl(ssl); subject.getPrincipals().remove(userPrincipal); succeeded = false; commitSucceeded = false; alias = null; userPrincipal = null; return true; } public static void setKeyStore(KeyStore keyStore) { ClientCertificateLoginModule.keyStore = keyStore; } private Callback createChoiceCallback(String[] choices) { return new ChoiceCallback(localStrings.getLocalString("login.certificate", "Choose from list of certificates: "), choices, 0, false); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy