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 */
041public class JaasCertificateAuthenticationBroker extends BrokerFilter implements AuthenticationBroker {
042    private final String jaasConfiguration;
043
044    /**
045     * Simple constructor. Leaves everything to superclass.
046     *
047     * @param next The Broker that does the actual work for this Filter.
048     * @param jaasConfiguration The JAAS domain configuration name (refere to
049     *                JAAS documentation).
050     */
051    public JaasCertificateAuthenticationBroker(Broker next, String jaasConfiguration) {
052        super(next);
053
054        this.jaasConfiguration = jaasConfiguration;
055    }
056
057    /**
058     * Overridden to allow for authentication based on client certificates.
059     * Connections being added will be authenticated based on their certificate
060     * chain and the JAAS module specified through the JAAS framework. NOTE: The
061     * security context's username will be set to the first UserPrincipal
062     * created by the login module.
063     *
064     * @param context The context for the incoming Connection.
065     * @param info The ConnectionInfo Command representing the incoming
066     *                connection.
067     */
068    @Override
069    public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
070
071        if (context.getSecurityContext() == null) {
072            if (!(info.getTransportContext() instanceof X509Certificate[])) {
073                throw new SecurityException("Unable to authenticate transport without SSL certificate.");
074            }
075
076            // Set the TCCL since it seems JAAS needs it to find the login
077            // module classes.
078            ClassLoader original = Thread.currentThread().getContextClassLoader();
079            Thread.currentThread().setContextClassLoader(JaasAuthenticationBroker.class.getClassLoader());
080            try {
081                SecurityContext s = authenticate(info.getUserName(), info.getPassword(), (X509Certificate[]) info.getTransportContext());
082                context.setSecurityContext(s);
083            } finally {
084                Thread.currentThread().setContextClassLoader(original);
085            }
086        }
087        super.addConnection(context, info);
088    }
089
090    /**
091     * Overriding removeConnection to make sure the security context is cleaned.
092     */
093    @Override
094    public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
095        super.removeConnection(context, info, error);
096
097        context.setSecurityContext(null);
098    }
099
100    @Override
101    public SecurityContext authenticate(String username, String password, X509Certificate[] peerCertificates) throws SecurityException {
102        try {
103            CallbackHandler callback = new JaasCertificateCallbackHandler(peerCertificates);
104            LoginContext lc = new LoginContext(jaasConfiguration, callback);
105            lc.login();
106            Subject subject = lc.getSubject();
107
108            String dnName = "";
109
110            for (Principal principal : subject.getPrincipals()) {
111                if (principal instanceof UserPrincipal) {
112                    dnName = ((UserPrincipal)principal).getName();
113                    break;
114                }
115            }
116
117            return new JaasCertificateSecurityContext(dnName, subject, peerCertificates);
118        } catch (Exception e) {
119            throw new SecurityException("User name [" + username + "] or password is invalid. " + e.getMessage(), e);
120        }
121    }
122}