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
079                connectionInfo.setUserName(mechanism.getUsername());
080                connectionInfo.setPassword(mechanism.getPassword());
081
082                if (tryAuthenticate(connectionInfo, transport.getPeerCertificates())) {
083                    sasl.done(Sasl.SaslOutcome.PN_SASL_OK);
084                } else {
085                    sasl.done(Sasl.SaslOutcome.PN_SASL_AUTH);
086                }
087
088                LOG.debug("SASL [{}} Handshake complete.", mechanism.getMechanismName());
089            } else {
090                LOG.info("SASL: could not find supported mechanism");
091                sasl.done(Sasl.SaslOutcome.PN_SASL_PERM);
092            }
093        }
094    }
095
096    //----- Internal implementation ------------------------------------------//
097
098    private SaslMechanism getSaslMechanism(String[] remoteMechanisms) {
099        String primary = remoteMechanisms[0];
100
101        if (primary.equalsIgnoreCase("PLAIN")) {
102            return new PlainMechanism();
103        } else if (primary.equalsIgnoreCase("ANONYMOUS")) {
104            return new AnonymousMechanism();
105        }
106
107        return null;
108    }
109
110    private boolean tryAuthenticate(ConnectionInfo info, X509Certificate[] peerCertificates) {
111        try {
112            return getAuthenticator().authenticate(info.getUserName(), info.getPassword(), peerCertificates) != null;
113        } catch (Throwable error) {
114            return false;
115        }
116    }
117
118    private AuthenticationBroker getAuthenticator() {
119        if (authenticator == null) {
120            try {
121                authenticator = (AuthenticationBroker) brokerService.getBroker().getAdaptor(AuthenticationBroker.class);
122            } catch (Exception e) {
123                LOG.debug("Failed to lookup AuthenticationBroker from Broker, will use a default Noop version.");
124            }
125
126            if (authenticator == null) {
127                authenticator = new DefaultAuthenticationBroker();
128            }
129        }
130
131        return authenticator;
132    }
133
134    private class DefaultAuthenticationBroker implements AuthenticationBroker {
135
136        @Override
137        public SecurityContext authenticate(String username, String password, X509Certificate[] peerCertificates) throws SecurityException {
138            return new SecurityContext(username) {
139
140                @Override
141                public Set<Principal> getPrincipals() {
142                    return null;
143                }
144            };
145        }
146    }
147}