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.security;
019
020import java.security.Principal;
021import java.security.cert.X509Certificate;
022import java.util.Iterator;
023
024import javax.security.auth.Subject;
025import javax.security.auth.callback.CallbackHandler;
026import javax.security.auth.login.LoginContext;
027
028import org.apache.activemq.broker.Broker;
029import org.apache.activemq.broker.BrokerFilter;
030import org.apache.activemq.broker.ConnectionContext;
031import org.apache.activemq.command.ConnectionInfo;
032import org.apache.activemq.jaas.JaasCertificateCallbackHandler;
033import 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 */
044public 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}