/** * * Copyright 2003-2007 Jive Software, 2014 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smack.sasl; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.sasl.packet.SaslStanzas.AuthMechanism; import org.jivesoftware.smack.sasl.packet.SaslStanzas.Response; import org.jivesoftware.smack.util.StringUtils; import javax.security.auth.callback.CallbackHandler; /** * Base class for SASL mechanisms. Subclasses must implement these methods: * * Subclasses will likely want to implement their own versions of these methods: *
  • {@link #authenticate(String, String, String, String)} -- Initiate authentication stanza using the * deprecated method.
  • *
  • {@link #authenticate(String, String, CallbackHandler)} -- Initiate authentication stanza * using the CallbackHandler method.
  • *
  • {@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.
  • * * * Basic XMPP SASL authentication steps: * 1. Client authentication initialization, stanza sent to the server (Base64 encoded): * * 2. Server sends back to the client the challenge response (Base64 encoded) * sample: * realm=,nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess * 3. The client responds back to the server (Base 64 encoded): * sample: * username=,realm=,nonce="OA6MG9tEQGm2hh", * cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth, * digest-uri=, * response=d388dad90d4bbd760a152321f2143af7, * charset=utf-8, * authzid= * 4. The server evaluates if the user is present and contained in the REALM * if successful it sends: (Base64 encoded) * if not successful it sends: * sample: * * cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA== * * * @author Jay Kline */ public abstract class SASLMechanism implements Comparable { public static final String CRAMMD5 = "CRAM-MD5"; public static final String DIGESTMD5 = "DIGEST-MD5"; public static final String EXTERNAL = "EXTERNAL"; public static final String GSSAPI = "GSSAPI"; public static final String PLAIN = "PLAIN"; protected XMPPConnection connection; /** * authcid */ protected String authenticationId; /** * The name of the XMPP service */ protected String serviceName; /** * The users password */ protected String password; protected String host; /** * Builds and sends the auth stanza to the server. Note that this method of * authentication is not recommended, since it is very inflexible. Use * {@link #authenticate(String, String, CallbackHandler)} whenever possible. * * Explanation of auth stanza: * * The client authentication stanza needs to include the digest-uri of the form: xmpp/serviceName * From RFC-2831: * digest-uri = "digest-uri" "=" digest-uri-value * digest-uri-value = serv-type "/" host [ "/" serv-name ] * * digest-uri: * Indicates the principal name of the service with which the client * wishes to connect, formed from the serv-type, host, and serv-name. * For example, the FTP service * on "ftp.example.com" would have a "digest-uri" value of "ftp/ftp.example.com"; the SMTP * server from the example above would have a "digest-uri" value of * "smtp/mail3.example.com/example.com". * * host: * The DNS host name or IP address for the service requested. The DNS host name * must be the fully-qualified canonical name of the host. The DNS host name is the * preferred form; see notes on server processing of the digest-uri. * * serv-name: * Indicates the name of the service if it is replicated. The service is * considered to be replicated if the client's service-location process involves resolution * using standard DNS lookup operations, and if these operations involve DNS records (such * as SRV, or MX) which resolve one DNS name into a set of other DNS names. In this case, * the initial name used by the client is the "serv-name", and the final name is the "host" * component. For example, the incoming mail service for "example.com" may be replicated * through the use of MX records stored in the DNS, one of which points at an SMTP server * called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be * "mail3.example.com". If the service is not replicated, or the serv-name is identical to * the host, then the serv-name component MUST be omitted * * digest-uri verification is needed for ejabberd 2.0.3 and higher * * @param username the username of the user being authenticated. * @param host the hostname where the user account resides. * @param serviceName the xmpp service location - used by the SASL client in digest-uri creation * serviceName format is: host [ "/" serv-name ] as per RFC-2831 * @param password the password for this account. * @throws SmackException If a network error occurs while authenticating. * @throws NotConnectedException */ public final void authenticate(String username, String host, String serviceName, String password) throws SmackException, NotConnectedException { this.authenticationId = username; this.host = host; this.serviceName = serviceName; this.password = password; authenticateInternal(); authenticate(); } protected void authenticateInternal() throws SmackException { } /** * Builds and sends the auth stanza to the server. The callback handler will handle * any additional information, such as the authentication ID or realm, if it is needed. * * @param host the hostname where the user account resides. * @param serviceName the xmpp service location * @param cbh the CallbackHandler to obtain user information. * @throws SmackException * @throws NotConnectedException */ public void authenticate(String host,String serviceName, CallbackHandler cbh) throws SmackException, NotConnectedException { this.host = host; this.serviceName = serviceName; authenticateInternal(cbh); authenticate(); } protected abstract void authenticateInternal(CallbackHandler cbh) throws SmackException; private final void authenticate() throws SmackException, NotConnectedException { String authenticationText = getAuthenticationText(); // Send the authentication to the server connection.sendPacket(new AuthMechanism(getName(), authenticationText)); } protected abstract String getAuthenticationText() throws SmackException; /** * The server is challenging the SASL mechanism for the stanza he just sent. Send a * response to the server's challenge. * * @param challengeString a base64 encoded string representing the challenge. * @param finalChallenge true if this is the last challenge send by the server within the success stanza * @throws NotConnectedException * @throws SmackException */ public final void challengeReceived(String challengeString, boolean finalChallenge) throws SmackException, NotConnectedException { byte[] challenge = StringUtils.decodeBase64(challengeString); byte response[] = evaluateChallenge(challenge); if (finalChallenge) { return; } Response responseStanza; if (response == null) { responseStanza = new Response(); } else { responseStanza = new Response(StringUtils.encodeBase64(response, false)); } // Send the authentication to the server connection.sendPacket(responseStanza); } protected byte[] evaluateChallenge(byte[] challenge) throws SmackException { return null; } public final int compareTo(SASLMechanism other) { return getPriority() - other.getPriority(); } /** * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI. * * @return the common name of the SASL mechanism. */ public abstract String getName(); public abstract int getPriority(); public SASLMechanism instanceForAuthentication(XMPPConnection connection) { SASLMechanism saslMechansim = newInstance(); saslMechansim.connection = connection; return saslMechansim; } protected abstract SASLMechanism newInstance(); }