SASLMechanism.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software, 2014 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.sasl;

  18. import org.jivesoftware.smack.SmackException;
  19. import org.jivesoftware.smack.SmackException.NotConnectedException;
  20. import org.jivesoftware.smack.XMPPConnection;
  21. import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
  22. import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
  23. import org.jivesoftware.smack.util.StringTransformer;
  24. import org.jivesoftware.smack.util.StringUtils;
  25. import org.jivesoftware.smack.util.stringencoder.Base64;
  26. import org.jxmpp.jid.DomainBareJid;

  27. import javax.security.auth.callback.CallbackHandler;

  28. /**
  29.  * Base class for SASL mechanisms. Subclasses must implement these methods:
  30.  * <ul>
  31.  *  <li>{@link #getName()} -- returns the common name of the SASL mechanism.</li>
  32.  * </ul>
  33.  * Subclasses will likely want to implement their own versions of these methods:
  34.  *  <li>{@link #authenticate(String, String, DomainBareJid, String)} -- Initiate authentication stanza using the
  35.  *  deprecated method.</li>
  36.  *  <li>{@link #authenticate(String, DomainBareJid, CallbackHandler)} -- Initiate authentication stanza
  37.  *  using the CallbackHandler method.</li>
  38.  *  <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li>
  39.  * </ul>
  40.  *
  41.  * Basic XMPP SASL authentication steps:
  42.  * 1. Client authentication initialization, stanza sent to the server (Base64 encoded):
  43.  *    <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
  44.  * 2. Server sends back to the client the challenge response (Base64 encoded)
  45.  *    sample:
  46.  *    realm=<sasl server realm>,nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess
  47.  * 3. The client responds back to the server (Base 64 encoded):
  48.  *    sample:
  49.  *    username=<userid>,realm=<sasl server realm from above>,nonce="OA6MG9tEQGm2hh",
  50.  *    cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth,
  51.  *    digest-uri=<digesturi>,
  52.  *    response=d388dad90d4bbd760a152321f2143af7,
  53.  *    charset=utf-8,
  54.  *    authzid=<id>
  55.  * 4. The server evaluates if the user is present and contained in the REALM
  56.  *    if successful it sends: <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> (Base64 encoded)
  57.  *    if not successful it sends:
  58.  *    sample:
  59.  *    <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
  60.  *        cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
  61.  *    </challenge>
  62.  *
  63.  * @author Jay Kline
  64.  */
  65. public abstract class SASLMechanism implements Comparable<SASLMechanism> {

  66.     public static final String CRAMMD5 = "CRAM-MD5";
  67.     public static final String DIGESTMD5 = "DIGEST-MD5";
  68.     public static final String EXTERNAL = "EXTERNAL";
  69.     public static final String GSSAPI = "GSSAPI";
  70.     public static final String PLAIN = "PLAIN";

  71.     // TODO Remove once Smack's min Android API is 9, where java.text.Normalizer is available
  72.     private static StringTransformer saslPrepTransformer;

  73.     /**
  74.      * Set the SASLPrep StringTransformer.
  75.      * <p>
  76.      * A simple SASLPrep StringTransformer would be for example: <code>java.text.Normalizer.normalize(string, Form.NFKC);</code>
  77.      * </p>
  78.      *
  79.      * @param stringTransformer set StringTransformer to use for SASLPrep.
  80.      * @see <a href="http://tools.ietf.org/html/rfc4013">RFC 4013 - SASLprep: Stringprep Profile for User Names and Passwords</a>
  81.      */
  82.     public static void setSaslPrepTransformer(StringTransformer stringTransformer) {
  83.         saslPrepTransformer = stringTransformer;
  84.     }

  85.     protected XMPPConnection connection;

  86.     /**
  87.      * Then authentication identity (authcid). RFC 6120 § 6.3.7 informs us that some SASL mechanisms use this as a
  88.      * "simple user name". But the exact form is a matter of the mechanism and that it does not necessarily map to an
  89.      * localpart. But it usually is the localpart of the client JID, although sometimes other formats are used (e.g. the
  90.      * full JID).
  91.      * <p>
  92.      * Not to be confused with the authzid (see RFC 6120 § 6.3.8).
  93.      * </p>
  94.      */
  95.     protected String authenticationId;

  96.     /**
  97.      * The name of the XMPP service
  98.      */
  99.     protected DomainBareJid serviceName;

  100.     /**
  101.      * The users password
  102.      */
  103.     protected String password;
  104.     protected String host;

  105.     /**
  106.      * Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of
  107.      * authentication is not recommended, since it is very inflexible. Use
  108.      * {@link #authenticate(String, DomainBareJid, CallbackHandler)} whenever possible.
  109.      *
  110.      * Explanation of auth stanza:
  111.      *
  112.      * The client authentication stanza needs to include the digest-uri of the form: xmpp/serviceName
  113.      * From RFC-2831:
  114.      * digest-uri = "digest-uri" "=" digest-uri-value
  115.      * digest-uri-value = serv-type "/" host [ "/" serv-name ]
  116.      *
  117.      * digest-uri:
  118.      * Indicates the principal name of the service with which the client
  119.      * wishes to connect, formed from the serv-type, host, and serv-name.
  120.      * For example, the FTP service
  121.      * on "ftp.example.com" would have a "digest-uri" value of "ftp/ftp.example.com"; the SMTP
  122.      * server from the example above would have a "digest-uri" value of
  123.      * "smtp/mail3.example.com/example.com".
  124.      *
  125.      * host:
  126.      * The DNS host name or IP address for the service requested. The DNS host name
  127.      * must be the fully-qualified canonical name of the host. The DNS host name is the
  128.      * preferred form; see notes on server processing of the digest-uri.
  129.      *
  130.      * serv-name:
  131.      * Indicates the name of the service if it is replicated. The service is
  132.      * considered to be replicated if the client's service-location process involves resolution
  133.      * using standard DNS lookup operations, and if these operations involve DNS records (such
  134.      * as SRV, or MX) which resolve one DNS name into a set of other DNS names. In this case,
  135.      * the initial name used by the client is the "serv-name", and the final name is the "host"
  136.      * component. For example, the incoming mail service for "example.com" may be replicated
  137.      * through the use of MX records stored in the DNS, one of which points at an SMTP server
  138.      * called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be
  139.      * "mail3.example.com". If the service is not replicated, or the serv-name is identical to
  140.      * the host, then the serv-name component MUST be omitted
  141.      *
  142.      * digest-uri verification is needed for ejabberd 2.0.3 and higher  
  143.      *
  144.      * @param username the username of the user being authenticated.
  145.      * @param host the hostname where the user account resides.
  146.      * @param serviceName the xmpp service location - used by the SASL client in digest-uri creation
  147.      * serviceName format is: host [ "/" serv-name ] as per RFC-2831
  148.      * @param password the password for this account.
  149.      * @throws SmackException If a network error occurs while authenticating.
  150.      * @throws NotConnectedException
  151.      * @throws InterruptedException
  152.      */
  153.     public final void authenticate(String username, String host, DomainBareJid serviceName, String password)
  154.                     throws SmackException, NotConnectedException, InterruptedException {
  155.         this.authenticationId = username;
  156.         this.host = host;
  157.         this.serviceName = serviceName;
  158.         this.password = password;
  159.         authenticateInternal();
  160.         authenticate();
  161.     }

  162.     protected void authenticateInternal() throws SmackException {
  163.     }

  164.     /**
  165.      * Builds and sends the <tt>auth</tt> stanza to the server. The callback handler will handle
  166.      * any additional information, such as the authentication ID or realm, if it is needed.
  167.      *
  168.      * @param host     the hostname where the user account resides.
  169.      * @param serviceName the xmpp service location
  170.      * @param cbh      the CallbackHandler to obtain user information.
  171.      * @throws SmackException
  172.      * @throws NotConnectedException
  173.      * @throws InterruptedException
  174.      */
  175.     public void authenticate(String host, DomainBareJid serviceName, CallbackHandler cbh)
  176.                     throws SmackException, NotConnectedException, InterruptedException {
  177.         this.host = host;
  178.         this.serviceName = serviceName;
  179.         authenticateInternal(cbh);
  180.         authenticate();
  181.     }

  182.     protected abstract void authenticateInternal(CallbackHandler cbh) throws SmackException;

  183.     private final void authenticate() throws SmackException, NotConnectedException, InterruptedException {
  184.         byte[] authenticationBytes = getAuthenticationText();
  185.         String authenticationText;
  186.         if (authenticationBytes != null) {
  187.             authenticationText = Base64.encodeToString(authenticationBytes);
  188.         } else {
  189.             // RFC6120 6.4.2 "If the initiating entity needs to send a zero-length initial response,
  190.             // it MUST transmit the response as a single equals sign character ("="), which
  191.             // indicates that the response is present but contains no data."
  192.             authenticationText = "=";
  193.         }
  194.         // Send the authentication to the server
  195.         connection.send(new AuthMechanism(getName(), authenticationText));
  196.     }

  197.     /**
  198.      * Should return the initial response of the SASL mechanism. The returned byte array will be
  199.      * send base64 encoded to the server. SASL mechanism are free to return <code>null</code> here.
  200.      *
  201.      * @return the initial response or null
  202.      * @throws SmackException
  203.      */
  204.     protected abstract byte[] getAuthenticationText() throws SmackException;

  205.     /**
  206.      * The server is challenging the SASL mechanism for the stanza he just sent. Send a
  207.      * response to the server's challenge.
  208.      *
  209.      * @param challengeString a base64 encoded string representing the challenge.
  210.      * @param finalChallenge true if this is the last challenge send by the server within the success stanza
  211.      * @throws NotConnectedException
  212.      * @throws SmackException
  213.      * @throws InterruptedException
  214.      */
  215.     public final void challengeReceived(String challengeString, boolean finalChallenge) throws SmackException, NotConnectedException, InterruptedException {
  216.         byte[] challenge = Base64.decode(challengeString);
  217.         byte[] response = evaluateChallenge(challenge);
  218.         if (finalChallenge) {
  219.             return;
  220.         }

  221.         Response responseStanza;
  222.         if (response == null) {
  223.             responseStanza = new Response();
  224.         }
  225.         else {
  226.             responseStanza = new Response(Base64.encodeToString(response));
  227.         }

  228.         // Send the authentication to the server
  229.         connection.send(responseStanza);
  230.     }

  231.     protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
  232.         return null;
  233.     }

  234.     public final int compareTo(SASLMechanism other) {
  235.         return getPriority() - other.getPriority();
  236.     }

  237.     /**
  238.      * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI.
  239.      *
  240.      * @return the common name of the SASL mechanism.
  241.      */
  242.     public abstract String getName();

  243.     public abstract int getPriority();

  244.     public abstract void checkIfSuccessfulOrThrow() throws SmackException;

  245.     public SASLMechanism instanceForAuthentication(XMPPConnection connection) {
  246.         SASLMechanism saslMechansim = newInstance();
  247.         saslMechansim.connection = connection;
  248.         return saslMechansim;
  249.     }

  250.     protected abstract SASLMechanism newInstance();

  251.     protected static byte[] toBytes(String string) {
  252.         return StringUtils.toBytes(string);
  253.     }

  254.     /**
  255.      * SASLprep the given String.
  256.      *
  257.      * @param string the String to sasl prep.
  258.      * @return the given String SASL preped
  259.      * @see <a href="http://tools.ietf.org/html/rfc4013">RFC 4013 - SASLprep: Stringprep Profile for User Names and Passwords</a>
  260.      */
  261.     protected static String saslPrep(String string) {
  262.         StringTransformer stringTransformer = saslPrepTransformer;
  263.         if (stringTransformer != null) {
  264.             return stringTransformer.transform(string);
  265.         }
  266.         return string;
  267.     }
  268. }