001    /**
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  You may obtain a copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.apache.activemq.jaas;
020    
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    
024    import java.io.IOException;
025    import java.security.cert.X509Certificate;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.Map;
029    import java.util.Set;
030    
031    import javax.security.auth.Subject;
032    import javax.security.auth.callback.Callback;
033    import javax.security.auth.callback.CallbackHandler;
034    import javax.security.auth.callback.UnsupportedCallbackException;
035    import javax.security.auth.login.FailedLoginException;
036    import javax.security.auth.login.LoginException;
037    import javax.security.auth.spi.LoginModule;
038    
039    /**
040     * A LoginModule that allows for authentication based on SSL certificates.
041     * 
042     * Allows for subclasses to define methods used to verify user certificates and find user groups. 
043     * Uses CertificateCallbacks to retrieve certificates.
044     *  
045     * @author sepandm@gmail.com (Sepand)
046     *
047     */
048    public abstract class CertificateLoginModule implements LoginModule {
049        
050        private CallbackHandler callbackHandler;
051        private Subject subject;
052        
053        private X509Certificate certificates[];
054        private String username = null;
055        private Set groups = null;
056        
057        private Set principals = new HashSet();
058        
059        private static final Log log = LogFactory.getLog(CertificateLoginModule.class);
060        private boolean debug;
061    
062        /**
063         * Overriding to allow for proper initialization.
064         * 
065         * Standard JAAS.
066         */
067        public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
068            this.subject = subject;
069            this.callbackHandler = callbackHandler;
070            
071            debug = "true".equalsIgnoreCase((String) options.get("debug"));
072            
073            if (debug) {
074                log.debug("Initialized debug");
075            }
076        }
077    
078        /**
079         * Overriding to allow for certificate-based login.
080         * 
081         * Standard JAAS.
082         */
083        public boolean login() throws LoginException {
084            Callback[] callbacks = new Callback[1];
085    
086            callbacks[0] = new CertificateCallback();
087            try {
088                callbackHandler.handle(callbacks);
089            } catch (IOException ioe) {
090                throw new LoginException(ioe.getMessage());
091            } catch (UnsupportedCallbackException uce) {
092                throw new LoginException(uce.getMessage() + " Unable to obtain client certificates.");
093            }
094            certificates = ((CertificateCallback) callbacks[0]).getCertificates();
095            
096            username = getUserNameForCertificates(certificates);
097            if ( username == null )
098                throw new FailedLoginException("No user for client certificate: "
099                    + getDistinguishedName(certificates));
100    
101            groups = getUserGroups(username);
102            
103            if (debug) {
104                log.debug("Certificate for user: " + username);
105            }
106            return true;
107        }
108    
109        /**
110         * Overriding to complete login process.
111         * 
112         * Standard JAAS.
113         */
114        public boolean commit() throws LoginException {
115            principals.add(new UserPrincipal(username));
116    
117            String currentGroup = null;
118            for (Iterator iter = groups.iterator(); iter.hasNext(); ) {
119                currentGroup = (String)iter.next();
120                principals.add(new GroupPrincipal(currentGroup));
121            }
122    
123            subject.getPrincipals().addAll(principals);
124    
125            clear();
126    
127            if (debug) {
128                log.debug("commit");
129            }
130            return true;
131        }
132    
133        /**
134         * Standard JAAS override.
135         */
136        public boolean abort() throws LoginException {
137            clear();
138    
139            if (debug) {
140                log.debug("abort");
141            }
142            return true;
143        }
144    
145        /**
146         * Standard JAAS override.
147         */
148        public boolean logout() {
149            subject.getPrincipals().removeAll(principals);
150            principals.clear();
151    
152            if (debug) {
153                log.debug("logout");
154            }
155            return true;
156        }
157        
158        /**
159         * Helper method.
160         */
161        private void clear() {
162            groups.clear();
163            certificates = null;
164        }
165        
166        /**
167         * Should return a unique name corresponding to the certificates given.
168         * 
169         * The name returned will be used to look up access levels as well as
170         *      group associations.
171         *      
172         * @param dn The distinguished name.
173         * @return The unique name if the certificate is recognized, null otherwise.
174         */
175        protected abstract String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException;
176        
177        /**
178         * Should return a set of the groups this user belongs to.
179         * 
180         * The groups returned will be added to the user's credentials.
181         * 
182         * @param username The username of the client. This is the same name that
183         *      getUserNameForDn returned for the user's DN.
184         * @return A Set of the names of the groups this user belongs to.
185         */
186        protected abstract Set getUserGroups(final String username) throws LoginException;
187    
188        protected String getDistinguishedName(final X509Certificate[] certs) {
189            if (certs != null && certs.length > 0 && certs[0] != null) {
190                return certs[0].getSubjectDN().getName();
191            } else {
192                return null;
193            }
194        }
195    
196    }