001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2014-2018 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * 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.jivesoftware.smack.sasl;
018
019import java.text.Normalizer;
020import java.text.Normalizer.Form;
021
022import javax.net.ssl.SSLSession;
023import javax.security.auth.callback.CallbackHandler;
024
025import org.jivesoftware.smack.ConnectionConfiguration;
026import org.jivesoftware.smack.SmackException;
027import org.jivesoftware.smack.SmackException.NotConnectedException;
028import org.jivesoftware.smack.XMPPConnection;
029import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
030import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
031import org.jivesoftware.smack.util.StringUtils;
032import org.jivesoftware.smack.util.stringencoder.Base64;
033
034import org.jxmpp.jid.DomainBareJid;
035import org.jxmpp.jid.EntityBareJid;
036
037/**
038 * Base class for SASL mechanisms.
039 * Subclasses will likely want to implement their own versions of these methods:
040 * <ul>
041 *  <li>{@link #authenticate(String, String, DomainBareJid, String, EntityBareJid, SSLSession)} -- Initiate authentication stanza using the
042 *  deprecated method.</li>
043 *  <li>{@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} -- Initiate authentication stanza
044 *  using the CallbackHandler method.</li>
045 *  <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li>
046 * </ul>
047 *
048 * @author Jay Kline
049 * @author Florian Schmaus
050 */
051public abstract class SASLMechanism implements Comparable<SASLMechanism> {
052
053    public static final String CRAMMD5 = "CRAM-MD5";
054    public static final String DIGESTMD5 = "DIGEST-MD5";
055    public static final String EXTERNAL = "EXTERNAL";
056    public static final String GSSAPI = "GSSAPI";
057    public static final String PLAIN = "PLAIN";
058
059    protected XMPPConnection connection;
060
061    protected ConnectionConfiguration connectionConfiguration;
062
063    /**
064     * Then authentication identity (authcid). RFC 6120 § 6.3.7 informs us that some SASL mechanisms use this as a
065     * "simple user name". But the exact form is a matter of the mechanism and that it does not necessarily map to an
066     * localpart. But it usually is the localpart of the client JID, although sometimes other formats are used (e.g. the
067     * full JID).
068     * <p>
069     * Not to be confused with the authzid (see RFC 6120 § 6.3.8).
070     * </p>
071     */
072    protected String authenticationId;
073
074    /**
075     * The authorization identifier (authzid).
076     * This is always a bare Jid, but can be null.
077     */
078    protected EntityBareJid authorizationId;
079
080    /**
081     * The name of the XMPP service
082     */
083    protected DomainBareJid serviceName;
084
085    /**
086     * The users password
087     */
088    protected String password;
089    protected String host;
090
091    /**
092     * The used SSL/TLS session (if any).
093     */
094    protected SSLSession sslSession;
095
096    /**
097     * Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of
098     * authentication is not recommended, since it is very inflexible. Use
099     * {@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} whenever possible.
100     *
101     * Explanation of auth stanza:
102     *
103     * The client authentication stanza needs to include the digest-uri of the form: xmpp/serviceName
104     * From RFC-2831:
105     * digest-uri = "digest-uri" "=" digest-uri-value
106     * digest-uri-value = serv-type "/" host [ "/" serv-name ]
107     *
108     * digest-uri:
109     * Indicates the principal name of the service with which the client
110     * wishes to connect, formed from the serv-type, host, and serv-name.
111     * For example, the FTP service
112     * on "ftp.example.com" would have a "digest-uri" value of "ftp/ftp.example.com"; the SMTP
113     * server from the example above would have a "digest-uri" value of
114     * "smtp/mail3.example.com/example.com".
115     *
116     * host:
117     * The DNS host name or IP address for the service requested. The DNS host name
118     * must be the fully-qualified canonical name of the host. The DNS host name is the
119     * preferred form; see notes on server processing of the digest-uri.
120     *
121     * serv-name:
122     * Indicates the name of the service if it is replicated. The service is
123     * considered to be replicated if the client's service-location process involves resolution
124     * using standard DNS lookup operations, and if these operations involve DNS records (such
125     * as SRV, or MX) which resolve one DNS name into a set of other DNS names. In this case,
126     * the initial name used by the client is the "serv-name", and the final name is the "host"
127     * component. For example, the incoming mail service for "example.com" may be replicated
128     * through the use of MX records stored in the DNS, one of which points at an SMTP server
129     * called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be
130     * "mail3.example.com". If the service is not replicated, or the serv-name is identical to
131     * the host, then the serv-name component MUST be omitted
132     *
133     * digest-uri verification is needed for ejabberd 2.0.3 and higher
134     *
135     * @param username the username of the user being authenticated.
136     * @param host the hostname where the user account resides.
137     * @param serviceName the xmpp service location - used by the SASL client in digest-uri creation
138     * serviceName format is: host [ "/" serv-name ] as per RFC-2831
139     * @param password the password for this account.
140     * @param authzid the optional authorization identity.
141     * @param sslSession the optional SSL/TLS session (if one was established)
142     * @throws SmackException If a network error occurs while authenticating.
143     * @throws NotConnectedException
144     * @throws InterruptedException
145     */
146    public final void authenticate(String username, String host, DomainBareJid serviceName, String password,
147                    EntityBareJid authzid, SSLSession sslSession)
148                    throws SmackException, NotConnectedException, InterruptedException {
149        this.authenticationId = username;
150        this.host = host;
151        this.serviceName = serviceName;
152        this.password = password;
153        this.authorizationId = authzid;
154        this.sslSession = sslSession;
155        assert (authorizationId == null || authzidSupported());
156        authenticateInternal();
157        authenticate();
158    }
159
160    /**
161     * @throws SmackException
162     */
163    protected void authenticateInternal() throws SmackException {
164    }
165
166    /**
167     * Builds and sends the <tt>auth</tt> stanza to the server. The callback handler will handle
168     * any additional information, such as the authentication ID or realm, if it is needed.
169     *
170     * @param host     the hostname where the user account resides.
171     * @param serviceName the xmpp service location
172     * @param cbh      the CallbackHandler to obtain user information.
173     * @param authzid the optional authorization identity.
174     * @param sslSession the optional SSL/TLS session (if one was established)
175     * @throws SmackException
176     * @throws NotConnectedException
177     * @throws InterruptedException
178     */
179    public void authenticate(String host, DomainBareJid serviceName, CallbackHandler cbh, EntityBareJid authzid, SSLSession sslSession)
180                    throws SmackException, NotConnectedException, InterruptedException {
181        this.host = host;
182        this.serviceName = serviceName;
183        this.authorizationId = authzid;
184        this.sslSession = sslSession;
185        assert (authorizationId == null || authzidSupported());
186        authenticateInternal(cbh);
187        authenticate();
188    }
189
190    protected abstract void authenticateInternal(CallbackHandler cbh) throws SmackException;
191
192    private void authenticate() throws SmackException, NotConnectedException, InterruptedException {
193        byte[] authenticationBytes = getAuthenticationText();
194        String authenticationText;
195        // Some SASL mechanisms do return an empty array (e.g. EXTERNAL from javax), so check that
196        // the array is not-empty. Mechanisms are allowed to return either 'null' or an empty array
197        // if there is no authentication text.
198        if (authenticationBytes != null && authenticationBytes.length > 0) {
199            authenticationText = Base64.encodeToString(authenticationBytes);
200        } else {
201            // RFC6120 6.4.2 "If the initiating entity needs to send a zero-length initial response,
202            // it MUST transmit the response as a single equals sign character ("="), which
203            // indicates that the response is present but contains no data."
204            authenticationText = "=";
205        }
206        // Send the authentication to the server
207        connection.sendNonza(new AuthMechanism(getName(), authenticationText));
208    }
209
210    /**
211     * Should return the initial response of the SASL mechanism. The returned byte array will be
212     * send base64 encoded to the server. SASL mechanism are free to return <code>null</code> or an
213     * empty array here.
214     *
215     * @return the initial response or null
216     * @throws SmackException
217     */
218    protected abstract byte[] getAuthenticationText() throws SmackException;
219
220    /**
221     * The server is challenging the SASL mechanism for the stanza he just sent. Send a
222     * response to the server's challenge.
223     *
224     * @param challengeString a base64 encoded string representing the challenge.
225     * @param finalChallenge true if this is the last challenge send by the server within the success stanza
226     * @throws SmackException exception
227     * @throws InterruptedException if the connection is interrupted
228     */
229    public final void challengeReceived(String challengeString, boolean finalChallenge) throws SmackException, InterruptedException {
230        byte[] challenge = Base64.decode((challengeString != null && challengeString.equals("=")) ? "" : challengeString);
231        byte[] response = evaluateChallenge(challenge);
232        if (finalChallenge) {
233            return;
234        }
235
236        Response responseStanza;
237        if (response == null) {
238            responseStanza = new Response();
239        }
240        else {
241            responseStanza = new Response(Base64.encodeToString(response));
242        }
243
244        // Send the authentication to the server
245        connection.sendNonza(responseStanza);
246    }
247
248    /**
249     * Evaluate the SASL challenge.
250     *
251     * @param challenge challenge to evaluate.
252     *
253     * @return null.
254     * @throws SmackException in case of an error.
255     */
256    protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
257        return null;
258    }
259
260    @Override
261    public final int compareTo(SASLMechanism other) {
262        // Switch to Integer.compare(int, int) once Smack is on Android 19 or higher.
263        Integer ourPriority = getPriority();
264        return ourPriority.compareTo(other.getPriority());
265    }
266
267    /**
268     * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI.
269     *
270     * @return the common name of the SASL mechanism.
271     */
272    public abstract String getName();
273
274    /**
275     * Get the priority of this SASL mechanism. Lower values mean higher priority.
276     *
277     * @return the priority of this SASL mechanism.
278     */
279    public abstract int getPriority();
280
281    public abstract void checkIfSuccessfulOrThrow() throws SmackException;
282
283    public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) {
284        SASLMechanism saslMechansim = newInstance();
285        saslMechansim.connection = connection;
286        saslMechansim.connectionConfiguration = connectionConfiguration;
287        return saslMechansim;
288    }
289
290    public boolean authzidSupported() {
291        return false;
292    }
293
294    protected abstract SASLMechanism newInstance();
295
296    protected static byte[] toBytes(String string) {
297        return StringUtils.toUtf8Bytes(string);
298    }
299
300    /**
301     * SASLprep the given String. The resulting String is in UTF-8.
302     *
303     * @param string the String to sasl prep.
304     * @return the given String SASL preped
305     * @see <a href="http://tools.ietf.org/html/rfc4013">RFC 4013 - SASLprep: Stringprep Profile for User Names and Passwords</a>
306     */
307    protected static String saslPrep(String string) {
308        return Normalizer.normalize(string, Form.NFKC);
309    }
310
311    @Override
312    public final String toString() {
313        return "SASL Mech: " + getName() + ", Prio: " + getPriority();
314    }
315}