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 */
017package org.apache.activemq.transport.amqp.sasl;
018
019import java.security.Principal;
020import java.security.cert.X509Certificate;
021import java.util.Set;
022
023import org.apache.activemq.broker.BrokerService;
024import org.apache.activemq.command.ConnectionInfo;
025import org.apache.activemq.security.AuthenticationBroker;
026import org.apache.activemq.security.SecurityContext;
027import org.apache.activemq.transport.amqp.AmqpTransport;
028import org.apache.qpid.proton.engine.Sasl;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * SASL Authenitcation engine.
034 */
035public class AmqpAuthenticator {
036
037    private static final Logger LOG = LoggerFactory.getLogger(AmqpAuthenticator.class);
038
039    private static final String[] mechanisms = new String[] { "PLAIN", "ANONYMOUS" };
040
041    private final BrokerService brokerService;
042    private final AmqpTransport transport;
043    private final Sasl sasl;
044
045    private AuthenticationBroker authenticator;
046
047    public AmqpAuthenticator(AmqpTransport transport, Sasl sasl, BrokerService brokerService) {
048        this.brokerService = brokerService;
049        this.transport = transport;
050        this.sasl = sasl;
051
052        sasl.setMechanisms(mechanisms);
053        sasl.server();
054    }
055
056    /**
057     * @return true if the SASL exchange has conpleted, regardless of success.
058     */
059    public boolean isDone() {
060        return sasl.getOutcome() != Sasl.SaslOutcome.PN_SASL_NONE;
061    }
062
063    /**
064     * @return the list of all SASL mechanisms that are supported curretnly.
065     */
066    public String[] getSupportedMechanisms() {
067        return mechanisms;
068    }
069
070    public void processSaslExchange(ConnectionInfo connectionInfo) {
071        if (sasl.getRemoteMechanisms().length > 0) {
072
073            SaslMechanism mechanism = getSaslMechanism(sasl.getRemoteMechanisms());
074            if (mechanism != null) {
075                LOG.debug("SASL [{}} Handshake started.", mechanism.getMechanismName());
076
077                mechanism.processSaslStep(sasl);
078                if (!mechanism.isFailed()) {
079
080                    connectionInfo.setUserName(mechanism.getUsername());
081                    connectionInfo.setPassword(mechanism.getPassword());
082
083                    if (tryAuthenticate(connectionInfo, transport.getPeerCertificates())) {
084                        sasl.done(Sasl.SaslOutcome.PN_SASL_OK);
085                    } else {
086                        sasl.done(Sasl.SaslOutcome.PN_SASL_AUTH);
087                    }
088
089                    LOG.debug("SASL [{}} Handshake complete.", mechanism.getMechanismName());
090                } else {
091                    LOG.debug("SASL [{}} Handshake failed: {}", mechanism.getMechanismName(), mechanism.getFailureReason());
092                    sasl.done(Sasl.SaslOutcome.PN_SASL_AUTH);
093                }
094            } else {
095                LOG.info("SASL: could not find supported mechanism");
096                sasl.done(Sasl.SaslOutcome.PN_SASL_PERM);
097            }
098        }
099    }
100
101    //----- Internal implementation ------------------------------------------//
102
103    private SaslMechanism getSaslMechanism(String[] remoteMechanisms) {
104        String primary = remoteMechanisms[0];
105
106        if (primary.equalsIgnoreCase("PLAIN")) {
107            return new PlainMechanism();
108        } else if (primary.equalsIgnoreCase("ANONYMOUS")) {
109            return new AnonymousMechanism();
110        }
111
112        return null;
113    }
114
115    private boolean tryAuthenticate(ConnectionInfo info, X509Certificate[] peerCertificates) {
116        try {
117            return getAuthenticator().authenticate(info.getUserName(), info.getPassword(), peerCertificates) != null;
118        } catch (Throwable error) {
119            return false;
120        }
121    }
122
123    private AuthenticationBroker getAuthenticator() {
124        if (authenticator == null) {
125            try {
126                authenticator = (AuthenticationBroker) brokerService.getBroker().getAdaptor(AuthenticationBroker.class);
127            } catch (Exception e) {
128                LOG.debug("Failed to lookup AuthenticationBroker from Broker, will use a default Noop version.");
129            }
130
131            if (authenticator == null) {
132                authenticator = new DefaultAuthenticationBroker();
133            }
134        }
135
136        return authenticator;
137    }
138
139    private class DefaultAuthenticationBroker implements AuthenticationBroker {
140
141        @Override
142        public SecurityContext authenticate(String username, String password, X509Certificate[] peerCertificates) throws SecurityException {
143            return new SecurityContext(username) {
144
145                @Override
146                public Set<Principal> getPrincipals() {
147                    return null;
148                }
149            };
150        }
151    }
152}