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.security;
019    
020    import java.security.Principal;
021    import java.security.cert.X509Certificate;
022    import java.util.Iterator;
023    
024    import javax.security.auth.Subject;
025    import javax.security.auth.callback.CallbackHandler;
026    import javax.security.auth.login.LoginContext;
027    
028    import org.apache.activemq.broker.Broker;
029    import org.apache.activemq.broker.BrokerFilter;
030    import org.apache.activemq.broker.ConnectionContext;
031    import org.apache.activemq.command.ConnectionInfo;
032    import org.apache.activemq.jaas.JaasCertificateCallbackHandler;
033    import org.apache.activemq.jaas.UserPrincipal;
034    
035    /**
036     * A JAAS Authentication Broker that uses SSL Certificates. This class will
037     * provide the JAAS framework with a JaasCertificateCallbackHandler that will
038     * grant JAAS access to incoming connections' SSL certificate chains. NOTE:
039     * There is a chance that the incoming connection does not have a valid
040     * certificate (has null).
041     * 
042     * @author sepandm@gmail.com (Sepand)
043     */
044    public class JaasCertificateAuthenticationBroker extends BrokerFilter {
045        private final String jaasConfiguration;
046    
047        /**
048         * Simple constructor. Leaves everything to superclass.
049         * 
050         * @param next The Broker that does the actual work for this Filter.
051         * @param jassConfiguration The JAAS domain configuration name (refere to
052         *                JAAS documentation).
053         */
054        public JaasCertificateAuthenticationBroker(Broker next, String jaasConfiguration) {
055            super(next);
056    
057            this.jaasConfiguration = jaasConfiguration;
058        }
059    
060        /**
061         * Overridden to allow for authentication based on client certificates.
062         * Connections being added will be authenticated based on their certificate
063         * chain and the JAAS module specified through the JAAS framework. NOTE: The
064         * security context's username will be set to the first UserPrincipal
065         * created by the login module.
066         * 
067         * @param context The context for the incoming Connection.
068         * @param info The ConnectionInfo Command representing the incoming
069         *                connection.
070         */
071        public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
072    
073            if (context.getSecurityContext() == null) {
074                if (!(info.getTransportContext() instanceof X509Certificate[])) {
075                    throw new SecurityException("Unable to authenticate transport without SSL certificate.");
076                }
077    
078                // Set the TCCL since it seems JAAS needs it to find the login
079                // module classes.
080                ClassLoader original = Thread.currentThread().getContextClassLoader();
081                Thread.currentThread().setContextClassLoader(JaasAuthenticationBroker.class.getClassLoader());
082                try {
083                    // Do the login.
084                    try {
085                        CallbackHandler callback = new JaasCertificateCallbackHandler((X509Certificate[])info.getTransportContext());
086                        LoginContext lc = new LoginContext(jaasConfiguration, callback);
087                        lc.login();
088                        Subject subject = lc.getSubject();
089    
090                        String dnName = "";
091    
092                        for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext();) {
093                            Principal nextPrincipal = (Principal)iter.next();
094                            if (nextPrincipal instanceof UserPrincipal) {
095                                dnName = ((UserPrincipal)nextPrincipal).getName();
096                                break;
097                            }
098                        }
099                        SecurityContext s = new JaasCertificateSecurityContext(dnName, subject, (X509Certificate[])info.getTransportContext());
100                        context.setSecurityContext(s);
101                    } catch (Exception e) {
102                        throw new SecurityException("User name or password is invalid: " + e.getMessage(), e);
103                    }
104                } finally {
105                    Thread.currentThread().setContextClassLoader(original);
106                }
107            }
108            super.addConnection(context, info);
109        }
110    
111        /**
112         * Overriding removeConnection to make sure the security context is cleaned.
113         */
114        public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
115            super.removeConnection(context, info, error);
116    
117            context.setSecurityContext(null);
118        }
119    }