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 }