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