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