Smack/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java

232 lines
9.4 KiB
Java

/**
*
* 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:
* <ul>
* <li>{@link #getName()} -- returns the common name of the SASL mechanism.</li>
* </ul>
* Subclasses will likely want to implement their own versions of these methods:
* <li>{@link #authenticate(String, String, String, String)} -- Initiate authentication stanza using the
* deprecated method.</li>
* <li>{@link #authenticate(String, String, CallbackHandler)} -- Initiate authentication stanza
* using the CallbackHandler method.</li>
* <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li>
* </ul>
*
* Basic XMPP SASL authentication steps:
* 1. Client authentication initialization, stanza sent to the server (Base64 encoded):
* <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
* 2. Server sends back to the client the challenge response (Base64 encoded)
* sample:
* realm=<sasl server realm>,nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess
* 3. The client responds back to the server (Base 64 encoded):
* sample:
* username=<userid>,realm=<sasl server realm from above>,nonce="OA6MG9tEQGm2hh",
* cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth,
* digest-uri=<digesturi>,
* response=d388dad90d4bbd760a152321f2143af7,
* charset=utf-8,
* authzid=<id>
* 4. The server evaluates if the user is present and contained in the REALM
* if successful it sends: <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> (Base64 encoded)
* if not successful it sends:
* sample:
* <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
* cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
* </challenge>
*
* @author Jay Kline
*/
public abstract class SASLMechanism implements Comparable<SASLMechanism> {
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 <tt>auth</tt> 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 <tt>auth</tt> 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();
}