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