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 018package org.apache.activemq.jaas; 019 020import java.io.IOException; 021import java.security.Principal; 022import java.security.cert.X509Certificate; 023import java.util.HashSet; 024import java.util.Map; 025import java.util.Set; 026 027import javax.security.auth.Subject; 028import javax.security.auth.callback.Callback; 029import javax.security.auth.callback.CallbackHandler; 030import javax.security.auth.callback.UnsupportedCallbackException; 031import javax.security.auth.login.FailedLoginException; 032import javax.security.auth.login.LoginException; 033import javax.security.auth.spi.LoginModule; 034 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * A LoginModule that allows for authentication based on SSL certificates. 040 * Allows for subclasses to define methods used to verify user certificates and 041 * find user groups. Uses CertificateCallbacks to retrieve certificates. 042 * 043 * @author sepandm@gmail.com (Sepand) 044 */ 045public abstract class CertificateLoginModule extends PropertiesLoader implements LoginModule { 046 047 private static final Logger LOG = LoggerFactory.getLogger(CertificateLoginModule.class); 048 049 private CallbackHandler callbackHandler; 050 private Subject subject; 051 052 private String username; 053 private Set<Principal> principals = new HashSet<Principal>(); 054 055 /** the authentication status*/ 056 private boolean succeeded = false; 057 private boolean commitSucceeded = false; 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 init(options); 067 } 068 069 /** 070 * Overriding to allow for certificate-based login. Standard JAAS. 071 */ 072 @Override 073 public boolean login() throws LoginException { 074 Callback[] callbacks = new Callback[1]; 075 076 callbacks[0] = new CertificateCallback(); 077 try { 078 callbackHandler.handle(callbacks); 079 } catch (IOException ioe) { 080 throw new LoginException(ioe.getMessage()); 081 } catch (UnsupportedCallbackException uce) { 082 throw new LoginException(uce.getMessage() + " Unable to obtain client certificates."); 083 } 084 X509Certificate[] certificates = ((CertificateCallback)callbacks[0]).getCertificates(); 085 086 username = getUserNameForCertificates(certificates); 087 if (username == null) { 088 throw new FailedLoginException("No user for client certificate: " + getDistinguishedName(certificates)); 089 } 090 091 if (debug) { 092 LOG.debug("Certificate for user: " + username); 093 } 094 succeeded = true; 095 return true; 096 } 097 098 /** 099 * Overriding to complete login process. Standard JAAS. 100 */ 101 @Override 102 public boolean commit() throws LoginException { 103 if (debug) { 104 LOG.debug("commit"); 105 } 106 107 if (!succeeded) { 108 clear(); 109 return false; 110 } 111 112 principals.add(new UserPrincipal(username)); 113 114 for (String group : getUserGroups(username)) { 115 principals.add(new GroupPrincipal(group)); 116 } 117 118 subject.getPrincipals().addAll(principals); 119 120 username = null; 121 commitSucceeded = true; 122 return true; 123 } 124 125 /** 126 * Standard JAAS override. 127 */ 128 @Override 129 public boolean abort() throws LoginException { 130 if (debug) { 131 LOG.debug("abort"); 132 } 133 if (!succeeded) { 134 return false; 135 } else if (succeeded && commitSucceeded) { 136 // we succeeded, but another required module failed 137 logout(); 138 } else { 139 // our commit failed 140 clear(); 141 succeeded = false; 142 } 143 return true; 144 } 145 146 /** 147 * Standard JAAS override. 148 */ 149 @Override 150 public boolean logout() { 151 subject.getPrincipals().removeAll(principals); 152 clear(); 153 154 if (debug) { 155 LOG.debug("logout"); 156 } 157 158 succeeded = false; 159 commitSucceeded = false; 160 return true; 161 } 162 163 /** 164 * Helper method. 165 */ 166 private void clear() { 167 username = null; 168 principals.clear(); 169 } 170 171 /** 172 * Should return a unique name corresponding to the certificates given. The 173 * name returned will be used to look up access levels as well as group 174 * associations. 175 * 176 * @param certs The distinguished name. 177 * @return The unique name if the certificate is recognized, null otherwise. 178 */ 179 protected abstract String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException; 180 181 /** 182 * Should return a set of the groups this user belongs to. The groups 183 * returned will be added to the user's credentials. 184 * 185 * @param username The username of the client. This is the same name that 186 * getUserNameForDn returned for the user's DN. 187 * @return A Set of the names of the groups this user belongs to. 188 */ 189 protected abstract Set<String> getUserGroups(final String username) throws LoginException; 190 191 protected String getDistinguishedName(final X509Certificate[] certs) { 192 if (certs != null && certs.length > 0 && certs[0] != null) { 193 return certs[0].getSubjectDN().getName(); 194 } else { 195 return null; 196 } 197 } 198 199}