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.slf4j.Logger;
037 import org.slf4j.LoggerFactory;
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 Logger LOG = LoggerFactory.getLogger(CertificateLoginModule.class);
049
050 private CallbackHandler callbackHandler;
051 private Subject subject;
052
053 private X509Certificate certificates[];
054 private String username;
055 private Set<String> 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 @Override
063 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
064 this.subject = subject;
065 this.callbackHandler = callbackHandler;
066
067 debug = "true".equalsIgnoreCase((String)options.get("debug"));
068
069 if (debug) {
070 LOG.debug("Initialized debug");
071 }
072 }
073
074 /**
075 * Overriding to allow for certificate-based login. Standard JAAS.
076 */
077 @Override
078 public boolean login() throws LoginException {
079 Callback[] callbacks = new Callback[1];
080
081 callbacks[0] = new CertificateCallback();
082 try {
083 callbackHandler.handle(callbacks);
084 } catch (IOException ioe) {
085 throw new LoginException(ioe.getMessage());
086 } catch (UnsupportedCallbackException uce) {
087 throw new LoginException(uce.getMessage() + " Unable to obtain client certificates.");
088 }
089 certificates = ((CertificateCallback)callbacks[0]).getCertificates();
090
091 username = getUserNameForCertificates(certificates);
092 if (username == null) {
093 throw new FailedLoginException("No user for client certificate: " + getDistinguishedName(certificates));
094 }
095
096 groups = getUserGroups(username);
097
098 if (debug) {
099 LOG.debug("Certificate for user: " + username);
100 }
101 return true;
102 }
103
104 /**
105 * Overriding to complete login process. Standard JAAS.
106 */
107 @Override
108 public boolean commit() throws LoginException {
109 principals.add(new UserPrincipal(username));
110
111 for (String group : groups) {
112 principals.add(new GroupPrincipal(group));
113 }
114
115 subject.getPrincipals().addAll(principals);
116
117 clear();
118
119 if (debug) {
120 LOG.debug("commit");
121 }
122 return true;
123 }
124
125 /**
126 * Standard JAAS override.
127 */
128 @Override
129 public boolean abort() throws LoginException {
130 clear();
131
132 if (debug) {
133 LOG.debug("abort");
134 }
135 return true;
136 }
137
138 /**
139 * Standard JAAS override.
140 */
141 @Override
142 public boolean logout() {
143 subject.getPrincipals().removeAll(principals);
144 principals.clear();
145
146 if (debug) {
147 LOG.debug("logout");
148 }
149 return true;
150 }
151
152 /**
153 * Helper method.
154 */
155 private void clear() {
156 groups.clear();
157 certificates = null;
158 }
159
160 /**
161 * Should return a unique name corresponding to the certificates given. The
162 * name returned will be used to look up access levels as well as group
163 * associations.
164 *
165 * @param certs The distinguished name.
166 * @return The unique name if the certificate is recognized, null otherwise.
167 */
168 protected abstract String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException;
169
170 /**
171 * Should return a set of the groups this user belongs to. The groups
172 * returned will be added to the user's credentials.
173 *
174 * @param username The username of the client. This is the same name that
175 * getUserNameForDn returned for the user's DN.
176 * @return A Set of the names of the groups this user belongs to.
177 */
178 protected abstract Set<String> getUserGroups(final String username) throws LoginException;
179
180 protected String getDistinguishedName(final X509Certificate[] certs) {
181 if (certs != null && certs.length > 0 && certs[0] != null) {
182 return certs[0].getSubjectDN().getName();
183 } else {
184 return null;
185 }
186 }
187
188 }