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.Iterator; 025import java.util.Map; 026import java.util.Set; 027 028import javax.security.auth.Subject; 029import javax.security.auth.callback.Callback; 030import javax.security.auth.callback.CallbackHandler; 031import javax.security.auth.callback.UnsupportedCallbackException; 032import javax.security.auth.login.FailedLoginException; 033import javax.security.auth.login.LoginException; 034import javax.security.auth.spi.LoginModule; 035 036import org.slf4j.Logger; 037import 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 */ 046public 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}