mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-09-10 18:59:41 +02:00
Add smack-android and redesign SASL authentication
This commit marks an important milestone with the addition of the smack-android subproject. Smack is now able to run native on Android without requiring any modifications, which makes the aSmack build environment obsolete. It was necessary to redesign the code for SASL authentication to achieve this. Smack now comes with smack-sasl-provided for SASL implementations that do not rely on additional APIs like javax for platforms where those APIs are not available like Android.
This commit is contained in:
parent
5a2149718a
commit
89dc3a0e85
39 changed files with 1562 additions and 675 deletions
|
@ -33,8 +33,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.security.sasl.SaslException;
|
||||
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||
|
@ -60,7 +58,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
private static final String[] DEBUGGERS = new String[] {
|
||||
"org.jivesoftware.smackx.debugger.EnhancedDebugger",
|
||||
"org.jivesoftware.smackx.debugger.android.AndroidDebugger",
|
||||
"org.jivesoftware.smack.debugger.LiteDebugger" };
|
||||
"org.jivesoftware.smack.debugger.LiteDebugger",
|
||||
"org.jivesoftware.smack.debugger.ConsoleDebugger" };
|
||||
|
||||
/**
|
||||
* Counter to uniquely identify connections that are created.
|
||||
|
@ -347,9 +346,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
* @throws XMPPException if an error occurs on the XMPP protocol level.
|
||||
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
||||
* @throws IOException
|
||||
* @throws SaslException
|
||||
*/
|
||||
public void login(String username, String password) throws XMPPException, SmackException, SaslException, IOException {
|
||||
public void login(String username, String password) throws XMPPException, SmackException, IOException {
|
||||
login(username, password, "Smack");
|
||||
}
|
||||
|
||||
|
@ -378,9 +376,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
* @throws XMPPException if an error occurs on the XMPP protocol level.
|
||||
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
||||
* @throws IOException
|
||||
* @throws SaslException
|
||||
*/
|
||||
public abstract void login(String username, String password, String resource) throws XMPPException, SmackException, SaslException, IOException;
|
||||
public abstract void login(String username, String password, String resource) throws XMPPException, SmackException, IOException;
|
||||
|
||||
/**
|
||||
* Logs in to the server anonymously. Very few servers are configured to support anonymous
|
||||
|
@ -391,9 +388,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
* @throws XMPPException if an error occurs on the XMPP protocol level.
|
||||
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
||||
* @throws IOException
|
||||
* @throws SaslException
|
||||
*/
|
||||
public abstract void loginAnonymously() throws XMPPException, SmackException, SaslException, IOException;
|
||||
public abstract void loginAnonymously() throws XMPPException, SmackException, IOException;
|
||||
|
||||
/**
|
||||
* Notification message saying that the server requires the client to bind a
|
||||
|
|
|
@ -18,54 +18,37 @@
|
|||
package org.jivesoftware.smack;
|
||||
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.sasl.SASLAnonymous;
|
||||
import org.jivesoftware.smack.sasl.SASLCramMD5Mechanism;
|
||||
import org.jivesoftware.smack.sasl.SASLDigestMD5Mechanism;
|
||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||
import org.jivesoftware.smack.sasl.SASLExternalMechanism;
|
||||
import org.jivesoftware.smack.sasl.SASLGSSAPIMechanism;
|
||||
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||
import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.SASLPlainMechanism;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.sasl.SaslException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>This class is responsible authenticating the user using SASL, binding the resource
|
||||
* to the connection and establishing a session with the server.</p>
|
||||
*
|
||||
* <p>Once TLS has been negotiated (i.e. the connection has been secured) it is possible to
|
||||
* register with the server, authenticate using Non-SASL or authenticate using SASL. If the
|
||||
* server supports SASL then Smack will first try to authenticate using SASL. But if that
|
||||
* fails then Non-SASL will be tried.</p>
|
||||
* register with the server or authenticate using SASL. If the
|
||||
* server supports SASL then Smack will try to authenticate using SASL..</p>
|
||||
*
|
||||
* <p>The server may support many SASL mechanisms to use for authenticating. Out of the box
|
||||
* Smack provides several SASL mechanisms, but it is possible to register new SASL Mechanisms. Use
|
||||
* {@link #registerSASLMechanism(String, Class)} to register a new mechanisms. A registered
|
||||
* mechanism wont be used until {@link #supportSASLMechanism(String, int)} is called. By default,
|
||||
* the list of supported SASL mechanisms is determined from the {@link SmackConfiguration}. </p>
|
||||
*
|
||||
* <p>Once the user has been authenticated with SASL, it is necessary to bind a resource for
|
||||
* the connection. If no resource is passed in {@link #authenticate(String, String, String)}
|
||||
* then the server will assign a resource for the connection. In case a resource is passed
|
||||
* then the server will receive the desired resource but may assign a modified resource for
|
||||
* the connection.</p>
|
||||
*
|
||||
* <p>Once a resource has been binded and if the server supports sessions then Smack will establish
|
||||
* a session so that instant messaging and presence functionalities may be used.</p>
|
||||
* {@link #registerSASLMechanism(SASLMechanism)} to register a new mechanisms.
|
||||
*
|
||||
* @see org.jivesoftware.smack.sasl.SASLMechanism
|
||||
*
|
||||
|
@ -74,114 +57,91 @@ import java.util.Map;
|
|||
*/
|
||||
public class SASLAuthentication {
|
||||
|
||||
private static Map<String, Class<? extends SASLMechanism>> implementedMechanisms = new HashMap<String, Class<? extends SASLMechanism>>();
|
||||
private static List<String> mechanismsPreferences = new ArrayList<String>();
|
||||
private static final List<SASLMechanism> REGISTERED_MECHANISMS = new ArrayList<SASLMechanism>();
|
||||
|
||||
private XMPPConnection connection;
|
||||
private static final Set<String> BLACKLISTED_MECHANISMS = new HashSet<String>();
|
||||
|
||||
/**
|
||||
* Registers a new SASL mechanism
|
||||
*
|
||||
* @param mechanism a SASLMechanism subclass.
|
||||
*/
|
||||
public static void registerSASLMechanism(SASLMechanism mechanism) {
|
||||
synchronized (REGISTERED_MECHANISMS) {
|
||||
REGISTERED_MECHANISMS.add(mechanism);
|
||||
Collections.sort(REGISTERED_MECHANISMS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered SASLMechanism sorted by the level of preference.
|
||||
*
|
||||
* @return the registered SASLMechanism sorted by the level of preference.
|
||||
*/
|
||||
public static Map<String, String> getRegisterdSASLMechanisms() {
|
||||
Map<String, String> answer = new HashMap<String, String>();
|
||||
synchronized (REGISTERED_MECHANISMS) {
|
||||
for (SASLMechanism mechanism : REGISTERED_MECHANISMS) {
|
||||
answer.put(mechanism.getClass().getName(), mechanism.getName());
|
||||
}
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a SASLMechanism by it's full class name. For example
|
||||
* "org.jivesoftware.smack.sasl.javax.SASLCramMD5Mechanism".
|
||||
*
|
||||
* @param clazz the SASLMechanism class's name
|
||||
* @return true if the given SASLMechanism was removed, false otherwise
|
||||
*/
|
||||
public static boolean unregisterSASLMechanism(String clazz) {
|
||||
synchronized (REGISTERED_MECHANISMS) {
|
||||
Iterator<SASLMechanism> it = REGISTERED_MECHANISMS.iterator();
|
||||
while (it.hasNext()) {
|
||||
SASLMechanism mechanism = it.next();
|
||||
if (mechanism.getClass().getName().equals(clazz)) {
|
||||
it.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean blacklistSASLMechanism(String mechansim) {
|
||||
synchronized(BLACKLISTED_MECHANISMS) {
|
||||
return BLACKLISTED_MECHANISMS.add(mechansim);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean unBlacklistSASLMechanism(String mechanism) {
|
||||
synchronized(BLACKLISTED_MECHANISMS) {
|
||||
return BLACKLISTED_MECHANISMS.remove(mechanism);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> getBlacklistedSASLMechanisms() {
|
||||
synchronized(BLACKLISTED_MECHANISMS) {
|
||||
return new HashSet<String>(BLACKLISTED_MECHANISMS);
|
||||
}
|
||||
}
|
||||
|
||||
private final AbstractXMPPConnection connection;
|
||||
private Collection<String> serverMechanisms = new ArrayList<String>();
|
||||
private SASLMechanism currentMechanism = null;
|
||||
|
||||
/**
|
||||
* Boolean indicating if SASL negotiation has finished and was successful.
|
||||
*/
|
||||
private boolean saslNegotiated;
|
||||
|
||||
/**
|
||||
* The SASL related error condition if there was one provided by the server.
|
||||
* Either of type {@link SmackException} or {@link SASLErrorException}
|
||||
*/
|
||||
private SASLFailure saslFailure;
|
||||
private Exception saslException;
|
||||
|
||||
static {
|
||||
|
||||
// Register SASL mechanisms supported by Smack
|
||||
registerSASLMechanism("EXTERNAL", SASLExternalMechanism.class);
|
||||
registerSASLMechanism("GSSAPI", SASLGSSAPIMechanism.class);
|
||||
registerSASLMechanism("DIGEST-MD5", SASLDigestMD5Mechanism.class);
|
||||
registerSASLMechanism("CRAM-MD5", SASLCramMD5Mechanism.class);
|
||||
registerSASLMechanism("PLAIN", SASLPlainMechanism.class);
|
||||
registerSASLMechanism("ANONYMOUS", SASLAnonymous.class);
|
||||
|
||||
supportSASLMechanism("GSSAPI",0);
|
||||
supportSASLMechanism("DIGEST-MD5",1);
|
||||
supportSASLMechanism("CRAM-MD5",2);
|
||||
supportSASLMechanism("PLAIN",3);
|
||||
supportSASLMechanism("ANONYMOUS",4);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new SASL mechanism
|
||||
*
|
||||
* @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
|
||||
* @param mClass a SASLMechanism subclass.
|
||||
*/
|
||||
public static void registerSASLMechanism(String name, Class<? extends SASLMechanism> mClass) {
|
||||
implementedMechanisms.put(name, mClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an existing SASL mechanism. Once the mechanism has been unregistered it won't
|
||||
* be possible to authenticate users using the removed SASL mechanism. It also removes the
|
||||
* mechanism from the supported list.
|
||||
*
|
||||
* @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
|
||||
*/
|
||||
public static void unregisterSASLMechanism(String name) {
|
||||
implementedMechanisms.remove(name);
|
||||
mechanismsPreferences.remove(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers a new SASL mechanism in the specified preference position. The client will try
|
||||
* to authenticate using the most prefered SASL mechanism that is also supported by the server.
|
||||
* The SASL mechanism must be registered via {@link #registerSASLMechanism(String, Class)}
|
||||
*
|
||||
* @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
|
||||
*/
|
||||
public static void supportSASLMechanism(String name) {
|
||||
mechanismsPreferences.add(0, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new SASL mechanism in the specified preference position. The client will try
|
||||
* to authenticate using the most prefered SASL mechanism that is also supported by the server.
|
||||
* Use the <tt>index</tt> parameter to set the level of preference of the new SASL mechanism.
|
||||
* A value of 0 means that the mechanism is the most prefered one. The SASL mechanism must be
|
||||
* registered via {@link #registerSASLMechanism(String, Class)}
|
||||
*
|
||||
* @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
|
||||
* @param index preference position amongst all the implemented SASL mechanism. Starts with 0.
|
||||
*/
|
||||
public static void supportSASLMechanism(String name, int index) {
|
||||
mechanismsPreferences.add(index, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-supports an existing SASL mechanism. Once the mechanism has been unregistered it won't
|
||||
* be possible to authenticate users using the removed SASL mechanism. Note that the mechanism
|
||||
* is still registered, but will just not be used.
|
||||
*
|
||||
* @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
|
||||
*/
|
||||
public static void unsupportSASLMechanism(String name) {
|
||||
mechanismsPreferences.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registerd SASLMechanism classes sorted by the level of preference.
|
||||
*
|
||||
* @return the registerd SASLMechanism classes sorted by the level of preference.
|
||||
*/
|
||||
public static List<Class<? extends SASLMechanism>> getRegisterSASLMechanisms() {
|
||||
List<Class<? extends SASLMechanism>> answer = new ArrayList<Class<? extends SASLMechanism>>();
|
||||
for (String mechanismsPreference : mechanismsPreferences) {
|
||||
answer.add(implementedMechanisms.get(mechanismsPreference));
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
SASLAuthentication(XMPPConnection connection) {
|
||||
super();
|
||||
SASLAuthentication(AbstractXMPPConnection connection) {
|
||||
this.connection = connection;
|
||||
this.init();
|
||||
}
|
||||
|
@ -216,42 +176,16 @@ public class SASLAuthentication {
|
|||
* @param cbh the CallbackHandler used to get information from the user
|
||||
* @throws IOException
|
||||
* @throws XMPPErrorException
|
||||
* @throws NoResponseException
|
||||
* @throws SASLErrorException
|
||||
* @throws ResourceBindingNotOfferedException
|
||||
* @throws NotConnectedException
|
||||
* @throws SmackException
|
||||
*/
|
||||
public void authenticate(String resource, CallbackHandler cbh) throws IOException,
|
||||
NoResponseException, XMPPErrorException, SASLErrorException, ResourceBindingNotOfferedException, NotConnectedException {
|
||||
// Locate the SASLMechanism to use
|
||||
String selectedMechanism = null;
|
||||
for (String mechanism : mechanismsPreferences) {
|
||||
if (implementedMechanisms.containsKey(mechanism)
|
||||
&& serverMechanisms.contains(mechanism)) {
|
||||
selectedMechanism = mechanism;
|
||||
break;
|
||||
}
|
||||
}
|
||||
XMPPErrorException, SASLErrorException, SmackException {
|
||||
SASLMechanism selectedMechanism = selectMechanism();
|
||||
if (selectedMechanism != null) {
|
||||
// A SASL mechanism was found. Authenticate using the selected mechanism and then
|
||||
// proceed to bind a resource
|
||||
Class<? extends SASLMechanism> mechanismClass = implementedMechanisms.get(selectedMechanism);
|
||||
|
||||
Constructor<? extends SASLMechanism> constructor;
|
||||
try {
|
||||
constructor = mechanismClass.getConstructor(SASLAuthentication.class);
|
||||
currentMechanism = constructor.newInstance(this);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new SaslException("Exception when creating the SASLAuthentication instance",
|
||||
e);
|
||||
}
|
||||
|
||||
currentMechanism = selectedMechanism;
|
||||
synchronized (this) {
|
||||
// Trigger SASL authentication with the selected mechanism. We use
|
||||
// connection.getHost() since GSAPI requires the FQDN of the server, which
|
||||
// may not match the XMPP domain.
|
||||
currentMechanism.authenticate(connection.getHost(), cbh);
|
||||
currentMechanism.authenticate(connection.getHost(), connection.getServiceName(), cbh);
|
||||
try {
|
||||
// Wait until SASL negotiation finishes
|
||||
wait(connection.getPacketReplyTimeout());
|
||||
|
@ -261,18 +195,14 @@ public class SASLAuthentication {
|
|||
}
|
||||
}
|
||||
|
||||
if (saslFailure != null) {
|
||||
// SASL authentication failed and the server may have closed the connection
|
||||
// so throw an exception
|
||||
throw new SASLErrorException(selectedMechanism, saslFailure);
|
||||
}
|
||||
maybeThrowException();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
throw new NoResponseException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new SaslException(
|
||||
throw new SmackException(
|
||||
"SASL Authentication failed. No known authentication mechanisims.");
|
||||
}
|
||||
}
|
||||
|
@ -291,47 +221,18 @@ public class SASLAuthentication {
|
|||
* @throws XMPPErrorException
|
||||
* @throws SASLErrorException
|
||||
* @throws IOException
|
||||
* @throws SaslException
|
||||
* @throws SmackException
|
||||
*/
|
||||
public void authenticate(String username, String password, String resource)
|
||||
throws XMPPErrorException, SASLErrorException, SaslException, IOException,
|
||||
throws XMPPErrorException, SASLErrorException, IOException,
|
||||
SmackException {
|
||||
// Locate the SASLMechanism to use
|
||||
String selectedMechanism = null;
|
||||
for (String mechanism : mechanismsPreferences) {
|
||||
if (implementedMechanisms.containsKey(mechanism)
|
||||
&& serverMechanisms.contains(mechanism)) {
|
||||
selectedMechanism = mechanism;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SASLMechanism selectedMechanism = selectMechanism();
|
||||
if (selectedMechanism != null) {
|
||||
// A SASL mechanism was found. Authenticate using the selected mechanism and then
|
||||
// proceed to bind a resource
|
||||
Class<? extends SASLMechanism> mechanismClass = implementedMechanisms.get(selectedMechanism);
|
||||
try {
|
||||
Constructor<? extends SASLMechanism> constructor = mechanismClass.getConstructor(SASLAuthentication.class);
|
||||
currentMechanism = constructor.newInstance(this);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new SaslException("Exception when creating the SASLAuthentication instance",
|
||||
e);
|
||||
}
|
||||
currentMechanism = selectedMechanism;
|
||||
|
||||
synchronized (this) {
|
||||
// Trigger SASL authentication with the selected mechanism. We use
|
||||
// connection.getHost() since GSAPI requires the FQDN of the server, which
|
||||
// may not match the XMPP domain.
|
||||
|
||||
// The serviceName is basically the value that XMPP server sends to the client as
|
||||
// being
|
||||
// the location of the XMPP service we are trying to connect to. This should have
|
||||
// the
|
||||
// format: host ["/" serv-name ] as per RFC-2831 guidelines
|
||||
String serviceName = connection.getServiceName();
|
||||
currentMechanism.authenticate(username, connection.getHost(), serviceName, password);
|
||||
|
||||
currentMechanism.authenticate(username, connection.getHost(),
|
||||
connection.getServiceName(), password);
|
||||
try {
|
||||
// Wait until SASL negotiation finishes
|
||||
wait(connection.getPacketReplyTimeout());
|
||||
|
@ -339,21 +240,16 @@ public class SASLAuthentication {
|
|||
catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (saslFailure != null) {
|
||||
// SASL authentication failed and the server may have closed the connection
|
||||
// so throw an exception
|
||||
throw new SASLErrorException(selectedMechanism, saslFailure);
|
||||
}
|
||||
maybeThrowException();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
throw new NoResponseException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new SaslException(
|
||||
throw new SmackException(
|
||||
"SASL Authentication failed. No known authentication mechanisims.");
|
||||
}
|
||||
}
|
||||
|
@ -367,14 +263,12 @@ public class SASLAuthentication {
|
|||
* no username.
|
||||
*
|
||||
* @throws SASLErrorException
|
||||
* @throws IOException
|
||||
* @throws SaslException
|
||||
* @throws XMPPErrorException if an error occures while authenticating.
|
||||
* @throws SmackException if there was no response from the server.
|
||||
*/
|
||||
public void authenticateAnonymously() throws SASLErrorException, SaslException, IOException,
|
||||
public void authenticateAnonymously() throws SASLErrorException,
|
||||
SmackException, XMPPErrorException {
|
||||
currentMechanism = new SASLAnonymous(this);
|
||||
currentMechanism = (new SASLAnonymous()).instanceForAuthentication(connection);
|
||||
|
||||
// Wait until SASL negotiation finishes
|
||||
synchronized (this) {
|
||||
|
@ -387,17 +281,24 @@ public class SASLAuthentication {
|
|||
}
|
||||
}
|
||||
|
||||
if (saslFailure != null) {
|
||||
// SASL authentication failed and the server may have closed the connection
|
||||
// so throw an exception
|
||||
throw new SASLErrorException(currentMechanism.toString(), saslFailure);
|
||||
}
|
||||
maybeThrowException();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
throw new NoResponseException();
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeThrowException() throws SmackException, SASLErrorException {
|
||||
if (saslException != null){
|
||||
if (saslException instanceof SmackException) {
|
||||
throw (SmackException) saslException;
|
||||
} else if (saslException instanceof SASLErrorException) {
|
||||
throw (SASLErrorException) saslException;
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected exception type" , saslException);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets the available SASL mechanism reported by the server. The server will report the
|
||||
* available SASL mechanism once the TLS negotiation was successful. This information is
|
||||
|
@ -411,33 +312,49 @@ public class SASLAuthentication {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user was able to authenticate with the server usins SASL.
|
||||
*
|
||||
* @return true if the user was able to authenticate with the server usins SASL.
|
||||
* Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set
|
||||
* to <code>false</code>.
|
||||
*
|
||||
* @param challenge a base64 encoded string representing the challenge.
|
||||
* @throws SmackException
|
||||
*/
|
||||
public boolean isAuthenticated() {
|
||||
return saslNegotiated;
|
||||
public void challengeReceived(String challenge) throws SmackException {
|
||||
challengeReceived(challenge, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The server is challenging the SASL authentication we just sent. Forward the challenge
|
||||
* to the current SASLMechanism we are using. The SASLMechanism will send a response to
|
||||
* to the current SASLMechanism we are using. The SASLMechanism will eventually send a response to
|
||||
* the server. The length of the challenge-response sequence varies according to the
|
||||
* SASLMechanism in use.
|
||||
*
|
||||
* @param challenge a base64 encoded string representing the challenge.
|
||||
* @throws IOException If a network error occures while authenticating.
|
||||
* @throws NotConnectedException
|
||||
* @param finalChallenge true if this is the last challenge send by the server within the success stanza
|
||||
* @throws SmackException
|
||||
*/
|
||||
public void challengeReceived(String challenge) throws IOException, NotConnectedException {
|
||||
currentMechanism.challengeReceived(challenge);
|
||||
public void challengeReceived(String challenge, boolean finalChallenge) throws SmackException {
|
||||
try {
|
||||
currentMechanism.challengeReceived(challenge, finalChallenge);
|
||||
} catch (SmackException e) {
|
||||
authenticationFailed(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification message saying that SASL authentication was successful. The next step
|
||||
* would be to bind the resource.
|
||||
* @throws SmackException
|
||||
*/
|
||||
public void authenticated() {
|
||||
public void authenticated(Success success) throws SmackException {
|
||||
// RFC6120 6.3.10 "At the end of the authentication exchange, the SASL server (the XMPP
|
||||
// "receiving entity") can include "additional data with success" if appropriate for the
|
||||
// SASL mechanism in use. In XMPP, this is done by including the additional data as the XML
|
||||
// character data of the <success/> element." The used SASL mechanism should be able to
|
||||
// verify the data send by the server in the success stanza, if any.
|
||||
if (success.getData() != null) {
|
||||
challengeReceived(success.getData(), true);
|
||||
}
|
||||
saslNegotiated = true;
|
||||
// Wake up the thread that is waiting in the #authenticate method
|
||||
synchronized (this) {
|
||||
|
@ -453,18 +370,17 @@ public class SASLAuthentication {
|
|||
* @see <a href="https://tools.ietf.org/html/rfc6120#section-6.5">RFC6120 6.5</a>
|
||||
*/
|
||||
public void authenticationFailed(SASLFailure saslFailure) {
|
||||
this.saslFailure = saslFailure;
|
||||
authenticationFailed(new SASLErrorException(currentMechanism.getName(), saslFailure));
|
||||
}
|
||||
|
||||
public void authenticationFailed(Exception exception) {
|
||||
saslException = exception;
|
||||
// Wake up the thread that is waiting in the #authenticate method
|
||||
synchronized (this) {
|
||||
notify();
|
||||
}
|
||||
}
|
||||
|
||||
public void send(Packet stanza) throws NotConnectedException {
|
||||
connection.sendPacket(stanza);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the internal state in order to be able to be reused. The authentication
|
||||
* is used by the connection at the first login and then reused after the connection
|
||||
|
@ -472,6 +388,28 @@ public class SASLAuthentication {
|
|||
*/
|
||||
protected void init() {
|
||||
saslNegotiated = false;
|
||||
saslFailure = null;
|
||||
saslException = null;
|
||||
}
|
||||
|
||||
private SASLMechanism selectMechanism() {
|
||||
// Locate the SASLMechanism to use
|
||||
SASLMechanism selectedMechanism = null;
|
||||
Iterator<SASLMechanism> it = REGISTERED_MECHANISMS.iterator();
|
||||
// Iterate in SASL Priority order over registered mechanisms
|
||||
while (it.hasNext()) {
|
||||
SASLMechanism mechanism = it.next();
|
||||
String mechanismName = mechanism.getName();
|
||||
synchronized (BLACKLISTED_MECHANISMS) {
|
||||
if (BLACKLISTED_MECHANISMS.contains(mechanismName)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (serverMechanisms.contains(mechanismName)) {
|
||||
// Create a new instance of the SASLMechanism for every authentication attempt.
|
||||
selectedMechanism = mechanism.instanceForAuthentication(connection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return selectedMechanism;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -432,7 +432,7 @@ public final class SmackConfiguration {
|
|||
if (SmackInitializer.class.isAssignableFrom(initClass)) {
|
||||
SmackInitializer initializer = (SmackInitializer) initClass.newInstance();
|
||||
List<Exception> exceptions = initializer.initialize();
|
||||
if (exceptions.size() == 0) {
|
||||
if (exceptions == null || exceptions.size() == 0) {
|
||||
LOGGER.log(Level.FINE, "Loaded SmackInitializer " + className);
|
||||
} else {
|
||||
for (Exception e : exceptions) {
|
||||
|
|
|
@ -1,334 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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.debugger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.*;
|
||||
import java.awt.event.*;
|
||||
import java.io.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.jivesoftware.smack.*;
|
||||
import org.jivesoftware.smack.packet.*;
|
||||
import org.jivesoftware.smack.util.*;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
|
||||
/**
|
||||
* The LiteDebugger is a very simple debugger that allows to debug sent, received and
|
||||
* interpreted messages.
|
||||
*
|
||||
* @author Gaston Dombiak
|
||||
*/
|
||||
public class LiteDebugger implements SmackDebugger {
|
||||
|
||||
private static final String NEWLINE = "\n";
|
||||
|
||||
private JFrame frame = null;
|
||||
private XMPPConnection connection = null;
|
||||
|
||||
private PacketListener listener = null;
|
||||
|
||||
private Writer writer;
|
||||
private Reader reader;
|
||||
private ReaderListener readerListener;
|
||||
private WriterListener writerListener;
|
||||
|
||||
public LiteDebugger(XMPPConnection connection, Writer writer, Reader reader) {
|
||||
this.connection = connection;
|
||||
this.writer = writer;
|
||||
this.reader = reader;
|
||||
createDebug();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the debug process, which is a GUI window that displays XML traffic.
|
||||
*/
|
||||
private void createDebug() {
|
||||
frame = new JFrame("Smack Debug Window -- " + connection.getServiceName() + ":" +
|
||||
connection.getPort());
|
||||
|
||||
// Add listener for window closing event
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosing(WindowEvent evt) {
|
||||
rootWindowClosing(evt);
|
||||
}
|
||||
});
|
||||
|
||||
// We'll arrange the UI into four tabs. The first tab contains all data, the second
|
||||
// client generated XML, the third server generated XML, and the fourth is packet
|
||||
// data from the server as seen by Smack.
|
||||
JTabbedPane tabbedPane = new JTabbedPane();
|
||||
|
||||
JPanel allPane = new JPanel();
|
||||
allPane.setLayout(new GridLayout(3, 1));
|
||||
tabbedPane.add("All", allPane);
|
||||
|
||||
// Create UI elements for client generated XML traffic.
|
||||
final JTextArea sentText1 = new JTextArea();
|
||||
final JTextArea sentText2 = new JTextArea();
|
||||
sentText1.setEditable(false);
|
||||
sentText2.setEditable(false);
|
||||
sentText1.setForeground(new Color(112, 3, 3));
|
||||
sentText2.setForeground(new Color(112, 3, 3));
|
||||
allPane.add(new JScrollPane(sentText1));
|
||||
tabbedPane.add("Sent", new JScrollPane(sentText2));
|
||||
|
||||
// Add pop-up menu.
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
JMenuItem menuItem1 = new JMenuItem("Copy");
|
||||
menuItem1.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// Get the clipboard
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
// Set the sent text as the new content of the clipboard
|
||||
clipboard.setContents(new StringSelection(sentText1.getText()), null);
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem menuItem2 = new JMenuItem("Clear");
|
||||
menuItem2.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
sentText1.setText("");
|
||||
sentText2.setText("");
|
||||
}
|
||||
});
|
||||
|
||||
// Add listener to the text area so the popup menu can come up.
|
||||
MouseListener popupListener = new PopupListener(menu);
|
||||
sentText1.addMouseListener(popupListener);
|
||||
sentText2.addMouseListener(popupListener);
|
||||
menu.add(menuItem1);
|
||||
menu.add(menuItem2);
|
||||
|
||||
// Create UI elements for server generated XML traffic.
|
||||
final JTextArea receivedText1 = new JTextArea();
|
||||
final JTextArea receivedText2 = new JTextArea();
|
||||
receivedText1.setEditable(false);
|
||||
receivedText2.setEditable(false);
|
||||
receivedText1.setForeground(new Color(6, 76, 133));
|
||||
receivedText2.setForeground(new Color(6, 76, 133));
|
||||
allPane.add(new JScrollPane(receivedText1));
|
||||
tabbedPane.add("Received", new JScrollPane(receivedText2));
|
||||
|
||||
// Add pop-up menu.
|
||||
menu = new JPopupMenu();
|
||||
menuItem1 = new JMenuItem("Copy");
|
||||
menuItem1.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// Get the clipboard
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
// Set the sent text as the new content of the clipboard
|
||||
clipboard.setContents(new StringSelection(receivedText1.getText()), null);
|
||||
}
|
||||
});
|
||||
|
||||
menuItem2 = new JMenuItem("Clear");
|
||||
menuItem2.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
receivedText1.setText("");
|
||||
receivedText2.setText("");
|
||||
}
|
||||
});
|
||||
|
||||
// Add listener to the text area so the popup menu can come up.
|
||||
popupListener = new PopupListener(menu);
|
||||
receivedText1.addMouseListener(popupListener);
|
||||
receivedText2.addMouseListener(popupListener);
|
||||
menu.add(menuItem1);
|
||||
menu.add(menuItem2);
|
||||
|
||||
// Create UI elements for interpreted XML traffic.
|
||||
final JTextArea interpretedText1 = new JTextArea();
|
||||
final JTextArea interpretedText2 = new JTextArea();
|
||||
interpretedText1.setEditable(false);
|
||||
interpretedText2.setEditable(false);
|
||||
interpretedText1.setForeground(new Color(1, 94, 35));
|
||||
interpretedText2.setForeground(new Color(1, 94, 35));
|
||||
allPane.add(new JScrollPane(interpretedText1));
|
||||
tabbedPane.add("Interpreted", new JScrollPane(interpretedText2));
|
||||
|
||||
// Add pop-up menu.
|
||||
menu = new JPopupMenu();
|
||||
menuItem1 = new JMenuItem("Copy");
|
||||
menuItem1.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// Get the clipboard
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
// Set the sent text as the new content of the clipboard
|
||||
clipboard.setContents(new StringSelection(interpretedText1.getText()), null);
|
||||
}
|
||||
});
|
||||
|
||||
menuItem2 = new JMenuItem("Clear");
|
||||
menuItem2.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
interpretedText1.setText("");
|
||||
interpretedText2.setText("");
|
||||
}
|
||||
});
|
||||
|
||||
// Add listener to the text area so the popup menu can come up.
|
||||
popupListener = new PopupListener(menu);
|
||||
interpretedText1.addMouseListener(popupListener);
|
||||
interpretedText2.addMouseListener(popupListener);
|
||||
menu.add(menuItem1);
|
||||
menu.add(menuItem2);
|
||||
|
||||
frame.getContentPane().add(tabbedPane);
|
||||
|
||||
frame.setSize(550, 400);
|
||||
frame.setVisible(true);
|
||||
|
||||
// Create a special Reader that wraps the main Reader and logs data to the GUI.
|
||||
ObservableReader debugReader = new ObservableReader(reader);
|
||||
readerListener = new ReaderListener() {
|
||||
public void read(String str) {
|
||||
int index = str.lastIndexOf(">");
|
||||
if (index != -1) {
|
||||
receivedText1.append(str.substring(0, index + 1));
|
||||
receivedText2.append(str.substring(0, index + 1));
|
||||
receivedText1.append(NEWLINE);
|
||||
receivedText2.append(NEWLINE);
|
||||
if (str.length() > index) {
|
||||
receivedText1.append(str.substring(index + 1));
|
||||
receivedText2.append(str.substring(index + 1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
receivedText1.append(str);
|
||||
receivedText2.append(str);
|
||||
}
|
||||
}
|
||||
};
|
||||
debugReader.addReaderListener(readerListener);
|
||||
|
||||
// Create a special Writer that wraps the main Writer and logs data to the GUI.
|
||||
ObservableWriter debugWriter = new ObservableWriter(writer);
|
||||
writerListener = new WriterListener() {
|
||||
public void write(String str) {
|
||||
sentText1.append(str);
|
||||
sentText2.append(str);
|
||||
if (str.endsWith(">")) {
|
||||
sentText1.append(NEWLINE);
|
||||
sentText2.append(NEWLINE);
|
||||
}
|
||||
}
|
||||
};
|
||||
debugWriter.addWriterListener(writerListener);
|
||||
|
||||
// Assign the reader/writer objects to use the debug versions. The packet reader
|
||||
// and writer will use the debug versions when they are created.
|
||||
reader = debugReader;
|
||||
writer = debugWriter;
|
||||
|
||||
// Create a thread that will listen for all incoming packets and write them to
|
||||
// the GUI. This is what we call "interpreted" packet data, since it's the packet
|
||||
// data as Smack sees it and not as it's coming in as raw XML.
|
||||
listener = new PacketListener() {
|
||||
public void processPacket(Packet packet) {
|
||||
interpretedText1.append(packet.toXML().toString());
|
||||
interpretedText2.append(packet.toXML().toString());
|
||||
interpretedText1.append(NEWLINE);
|
||||
interpretedText2.append(NEWLINE);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification that the root window is closing. Stop listening for received and
|
||||
* transmitted packets.
|
||||
*
|
||||
* @param evt the event that indicates that the root window is closing
|
||||
*/
|
||||
public void rootWindowClosing(WindowEvent evt) {
|
||||
connection.removePacketListener(listener);
|
||||
((ObservableReader)reader).removeReaderListener(readerListener);
|
||||
((ObservableWriter)writer).removeWriterListener(writerListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for debug window popup dialog events.
|
||||
*/
|
||||
private class PopupListener extends MouseAdapter {
|
||||
JPopupMenu popup;
|
||||
|
||||
PopupListener(JPopupMenu popupMenu) {
|
||||
popup = popupMenu;
|
||||
}
|
||||
|
||||
public void mousePressed(MouseEvent e) {
|
||||
maybeShowPopup(e);
|
||||
}
|
||||
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
maybeShowPopup(e);
|
||||
}
|
||||
|
||||
private void maybeShowPopup(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
popup.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Reader newConnectionReader(Reader newReader) {
|
||||
((ObservableReader)reader).removeReaderListener(readerListener);
|
||||
ObservableReader debugReader = new ObservableReader(newReader);
|
||||
debugReader.addReaderListener(readerListener);
|
||||
reader = debugReader;
|
||||
return reader;
|
||||
}
|
||||
|
||||
public Writer newConnectionWriter(Writer newWriter) {
|
||||
((ObservableWriter)writer).removeWriterListener(writerListener);
|
||||
ObservableWriter debugWriter = new ObservableWriter(newWriter);
|
||||
debugWriter.addWriterListener(writerListener);
|
||||
writer = debugWriter;
|
||||
return writer;
|
||||
}
|
||||
|
||||
public void userHasLogged(String user) {
|
||||
boolean isAnonymous = "".equals(XmppStringUtils.parseLocalpart(user));
|
||||
String title =
|
||||
"Smack Debug Window -- "
|
||||
+ (isAnonymous ? "" : XmppStringUtils.parseBareAddress(user))
|
||||
+ "@"
|
||||
+ connection.getServiceName()
|
||||
+ ":"
|
||||
+ connection.getPort();
|
||||
title += "/" + XmppStringUtils.parseResource(user);
|
||||
frame.setTitle(title);
|
||||
}
|
||||
|
||||
public Reader getReader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
public Writer getWriter() {
|
||||
return writer;
|
||||
}
|
||||
|
||||
public PacketListener getReaderListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
public PacketListener getWriterListener() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -16,10 +16,7 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.sasl;
|
||||
|
||||
import org.jivesoftware.smack.SASLAuthentication;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
||||
|
@ -30,32 +27,30 @@ import javax.security.auth.callback.CallbackHandler;
|
|||
*/
|
||||
public class SASLAnonymous extends SASLMechanism {
|
||||
|
||||
public SASLAnonymous(SASLAuthentication saslAuthentication) {
|
||||
super(saslAuthentication);
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
public String getName() {
|
||||
return "ANONYMOUS";
|
||||
}
|
||||
|
||||
public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, NotConnectedException {
|
||||
authenticate();
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 500;
|
||||
}
|
||||
|
||||
public void authenticate(String username, String host, String password) throws IOException, NotConnectedException {
|
||||
authenticate();
|
||||
@Override
|
||||
protected void authenticateInternal(CallbackHandler cbh)
|
||||
throws SmackException {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
protected void authenticate() throws IOException, NotConnectedException {
|
||||
// Send the authentication to the server
|
||||
getSASLAuthentication().send(new AuthMechanism(getName(), null));
|
||||
@Override
|
||||
protected String getAuthenticationText() throws SmackException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public void challengeReceived(String challenge) throws IOException, NotConnectedException {
|
||||
// Build the challenge response stanza encoding the response text
|
||||
// and send the authentication to the server
|
||||
getSASLAuthentication().send(new Response());
|
||||
@Override
|
||||
public SASLAnonymous newInstance() {
|
||||
return new SASLAnonymous();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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.SASLAuthentication;
|
||||
|
||||
/**
|
||||
* Implementation of the SASL CRAM-MD5 mechanism
|
||||
*
|
||||
* @author Jay Kline
|
||||
*/
|
||||
public class SASLCramMD5Mechanism extends SASLMechanism {
|
||||
|
||||
public SASLCramMD5Mechanism(SASLAuthentication saslAuthentication) {
|
||||
super(saslAuthentication);
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return "CRAM-MD5";
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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.SASLAuthentication;
|
||||
|
||||
/**
|
||||
* Implementation of the SASL DIGEST-MD5 mechanism
|
||||
*
|
||||
* @author Jay Kline
|
||||
*/
|
||||
public class SASLDigestMD5Mechanism extends SASLMechanism {
|
||||
|
||||
public SASLDigestMD5Mechanism(SASLAuthentication saslAuthentication) {
|
||||
super(saslAuthentication);
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return "DIGEST-MD5";
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
|
||||
public class SASLErrorException extends XMPPException {
|
||||
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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.SASLAuthentication;
|
||||
|
||||
/**
|
||||
* Implementation of the SASL EXTERNAL mechanism.
|
||||
*
|
||||
* To effectively use this mechanism, Java must be configured to properly
|
||||
* supply a client SSL certificate (of some sort) to the server. It is up
|
||||
* to the implementer to determine how to do this. Here is one method:
|
||||
*
|
||||
* Create a java keystore with your SSL certificate in it:
|
||||
* keytool -genkey -alias username -dname "cn=username,ou=organizationalUnit,o=organizationaName,l=locality,s=state,c=country"
|
||||
*
|
||||
* Next, set the System Properties:
|
||||
* <ul>
|
||||
* <li>javax.net.ssl.keyStore to the location of the keyStore
|
||||
* <li>javax.net.ssl.keyStorePassword to the password of the keyStore
|
||||
* <li>javax.net.ssl.trustStore to the location of the trustStore
|
||||
* <li>javax.net.ssl.trustStorePassword to the the password of the trustStore
|
||||
* </ul>
|
||||
*
|
||||
* Then, when the server requests or requires the client certificate, java will
|
||||
* simply provide the one in the keyStore.
|
||||
*
|
||||
* Also worth noting is the EXTERNAL mechanism in Smack is not enabled by default.
|
||||
* To enable it, the implementer will need to call SASLAuthentication.supportSASLMechamism("EXTERNAL");
|
||||
*
|
||||
* @author Jay Kline
|
||||
*/
|
||||
public class SASLExternalMechanism extends SASLMechanism {
|
||||
|
||||
public SASLExternalMechanism(SASLAuthentication saslAuthentication) {
|
||||
super(saslAuthentication);
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return "EXTERNAL";
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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.SASLAuthentication;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.security.sasl.Sasl;
|
||||
import javax.security.sasl.SaslException;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
||||
/**
|
||||
* Implementation of the SASL GSSAPI mechanism
|
||||
*
|
||||
* @author Jay Kline
|
||||
*/
|
||||
public class SASLGSSAPIMechanism extends SASLMechanism {
|
||||
|
||||
public SASLGSSAPIMechanism(SASLAuthentication saslAuthentication) {
|
||||
super(saslAuthentication);
|
||||
|
||||
System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
|
||||
System.setProperty("java.security.auth.login.config","gss.conf");
|
||||
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return "GSSAPI";
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and sends the <tt>auth</tt> stanza to the server.
|
||||
* This overrides from the abstract class because the initial token
|
||||
* needed for GSSAPI is binary, and not safe to put in a string, thus
|
||||
* getAuthenticationText() cannot be used.
|
||||
*
|
||||
* @param username the username of the user being authenticated.
|
||||
* @param host the hostname where the user account resides.
|
||||
* @param cbh the CallbackHandler (not used with GSSAPI)
|
||||
* @throws IOException If a network error occures while authenticating.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, SaslException, NotConnectedException {
|
||||
String[] mechanisms = { getName() };
|
||||
Map<String,String> props = new HashMap<String,String>();
|
||||
props.put(Sasl.SERVER_AUTH,"TRUE");
|
||||
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
|
||||
authenticate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and sends the <tt>auth</tt> stanza to the server.
|
||||
* This overrides from the abstract class because the initial token
|
||||
* needed for GSSAPI is binary, and not safe to put in a string, thus
|
||||
* getAuthenticationText() cannot be used.
|
||||
*
|
||||
* @param username the username of the user being authenticated.
|
||||
* @param host the hostname where the user account resides.
|
||||
* @param password the password of the user (ignored for GSSAPI)
|
||||
* @throws IOException If a network error occures while authenticating.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void authenticate(String username, String host, String password) throws IOException, SaslException, NotConnectedException {
|
||||
String[] mechanisms = { getName() };
|
||||
Map<String,String> props = new HashMap<String, String>();
|
||||
props.put(Sasl.SERVER_AUTH,"TRUE");
|
||||
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this);
|
||||
authenticate();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
* 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.
|
||||
|
@ -16,25 +16,14 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.sasl;
|
||||
|
||||
import org.jivesoftware.smack.SASLAuthentication;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
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 java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.NameCallback;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.sasl.RealmCallback;
|
||||
import javax.security.sasl.RealmChoiceCallback;
|
||||
import javax.security.sasl.Sasl;
|
||||
import javax.security.sasl.SaslClient;
|
||||
import javax.security.sasl.SaslException;
|
||||
|
||||
/**
|
||||
* Base class for SASL mechanisms. Subclasses must implement these methods:
|
||||
|
@ -44,9 +33,9 @@ import javax.security.sasl.SaslException;
|
|||
* 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, CallbackHandler)} -- Initiate authentication stanza
|
||||
* <li>{@link #authenticate(String, String, CallbackHandler)} -- Initiate authentication stanza
|
||||
* using the CallbackHandler method.</li>
|
||||
* <li>{@link #challengeReceived(String)} -- Handle a challenge from the server.</li>
|
||||
* <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Basic XMPP SASL authentication steps:
|
||||
|
@ -73,26 +62,40 @@ import javax.security.sasl.SaslException;
|
|||
*
|
||||
* @author Jay Kline
|
||||
*/
|
||||
public abstract class SASLMechanism implements CallbackHandler {
|
||||
public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||
|
||||
private SASLAuthentication saslAuthentication;
|
||||
protected SaslClient sc;
|
||||
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;
|
||||
protected String password;
|
||||
protected String hostname;
|
||||
|
||||
public SASLMechanism(SASLAuthentication saslAuthentication) {
|
||||
this.saslAuthentication = saslAuthentication;
|
||||
}
|
||||
/**
|
||||
* 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 inflexable. Use
|
||||
* {@link #authenticate(String, CallbackHandler)} whenever possible.
|
||||
* 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/serverName
|
||||
* 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 ]
|
||||
|
@ -129,70 +132,67 @@ public abstract class SASLMechanism implements CallbackHandler {
|
|||
* @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 IOException If a network error occurs while authenticating.
|
||||
* @throws SaslException
|
||||
* @throws SmackException If a network error occurs while authenticating.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void authenticate(String username, String host, String serviceName, String password) throws IOException, SaslException, NotConnectedException {
|
||||
//Since we were not provided with a CallbackHandler, we will use our own with the given
|
||||
//information
|
||||
|
||||
//Set the authenticationID as the username, since they must be the same in this case.
|
||||
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;
|
||||
this.hostname = host;
|
||||
|
||||
String[] mechanisms = { getName() };
|
||||
Map<String,String> props = new HashMap<String,String>();
|
||||
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", serviceName, props, this);
|
||||
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 IOException If a network error occures while authenticating.
|
||||
* @throws SaslException If a protocol error occurs or the user is not authenticated.
|
||||
* @throws SmackException
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void authenticate(String host, CallbackHandler cbh) throws IOException, SaslException, NotConnectedException {
|
||||
String[] mechanisms = { getName() };
|
||||
Map<String,String> props = new HashMap<String,String>();
|
||||
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
|
||||
public void authenticate(String host,String serviceName, CallbackHandler cbh)
|
||||
throws SmackException, NotConnectedException {
|
||||
this.host = host;
|
||||
this.serviceName = serviceName;
|
||||
authenticateInternal(cbh);
|
||||
authenticate();
|
||||
}
|
||||
|
||||
protected void authenticate() throws IOException, SaslException, NotConnectedException {
|
||||
String authenticationText = null;
|
||||
if (sc.hasInitialResponse()) {
|
||||
byte[] response = sc.evaluateChallenge(new byte[0]);
|
||||
authenticationText = StringUtils.encodeBase64(response, false);
|
||||
}
|
||||
protected abstract void authenticateInternal(CallbackHandler cbh) throws SmackException;
|
||||
|
||||
private final void authenticate() throws SmackException, NotConnectedException {
|
||||
String authenticationText = getAuthenticationText();
|
||||
// Send the authentication to the server
|
||||
getSASLAuthentication().send(new AuthMechanism(getName(), authenticationText));
|
||||
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 challenge a base64 encoded string representing the challenge.
|
||||
* @throws IOException if an exception sending the response occurs.
|
||||
* @throws NotConnectedException
|
||||
* @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 void challengeReceived(String challenge) throws IOException, NotConnectedException {
|
||||
byte response[];
|
||||
if(challenge != null) {
|
||||
response = sc.evaluateChallenge(StringUtils.decodeBase64(challenge));
|
||||
} else {
|
||||
response = sc.evaluateChallenge(new byte[0]);
|
||||
public final void challengeReceived(String challengeString, boolean finalChallenge) throws SmackException, NotConnectedException {
|
||||
byte[] challenge = StringUtils.decodeBase64(challengeString);
|
||||
byte response[] = evaluateChallenge(challenge);
|
||||
if (finalChallenge) {
|
||||
return;
|
||||
}
|
||||
|
||||
Packet responseStanza;
|
||||
Response responseStanza;
|
||||
if (response == null) {
|
||||
responseStanza = new Response();
|
||||
}
|
||||
|
@ -201,7 +201,15 @@ public abstract class SASLMechanism implements CallbackHandler {
|
|||
}
|
||||
|
||||
// Send the authentication to the server
|
||||
getSASLAuthentication().send(responseStanza);
|
||||
connection.sendPacket(responseStanza);
|
||||
}
|
||||
|
||||
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public final int compareTo(SASLMechanism other) {
|
||||
return getPriority() - other.getPriority();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,184 +217,15 @@ public abstract class SASLMechanism implements CallbackHandler {
|
|||
*
|
||||
* @return the common name of the SASL mechanism.
|
||||
*/
|
||||
protected abstract String getName();
|
||||
public abstract String getName();
|
||||
|
||||
protected SASLAuthentication getSASLAuthentication() {
|
||||
return saslAuthentication;
|
||||
public abstract int getPriority();
|
||||
|
||||
public SASLMechanism instanceForAuthentication(XMPPConnection connection) {
|
||||
SASLMechanism saslMechansim = newInstance();
|
||||
saslMechansim.connection = connection;
|
||||
return saslMechansim;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||
for (int i = 0; i < callbacks.length; i++) {
|
||||
if (callbacks[i] instanceof NameCallback) {
|
||||
NameCallback ncb = (NameCallback)callbacks[i];
|
||||
ncb.setName(authenticationId);
|
||||
} else if(callbacks[i] instanceof PasswordCallback) {
|
||||
PasswordCallback pcb = (PasswordCallback)callbacks[i];
|
||||
pcb.setPassword(password.toCharArray());
|
||||
} else if(callbacks[i] instanceof RealmCallback) {
|
||||
RealmCallback rcb = (RealmCallback)callbacks[i];
|
||||
//Retrieve the REALM from the challenge response that the server returned when the client initiated the authentication
|
||||
//exchange. If this value is not null or empty, *this value* has to be sent back to the server in the client's response
|
||||
//to the server's challenge
|
||||
String text = rcb.getDefaultText();
|
||||
//The SASL client (sc) created in smack uses rcb.getText when creating the negotiatedRealm to send it back to the server
|
||||
//Make sure that this value matches the server's realm
|
||||
rcb.setText(text);
|
||||
} else if(callbacks[i] instanceof RealmChoiceCallback){
|
||||
//unused
|
||||
//RealmChoiceCallback rccb = (RealmChoiceCallback)callbacks[i];
|
||||
} else {
|
||||
throw new UnsupportedCallbackException(callbacks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiating SASL authentication by select a mechanism.
|
||||
*/
|
||||
public static class AuthMechanism extends Packet {
|
||||
final private String name;
|
||||
final private String authenticationText;
|
||||
|
||||
public AuthMechanism(String name, String authenticationText) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("SASL mechanism name shouldn't be null.");
|
||||
}
|
||||
this.name = name;
|
||||
this.authenticationText = authenticationText;
|
||||
}
|
||||
|
||||
public String toXML() {
|
||||
StringBuilder stanza = new StringBuilder();
|
||||
stanza.append("<auth mechanism=\"").append(name);
|
||||
stanza.append("\" xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
|
||||
if (authenticationText != null &&
|
||||
authenticationText.trim().length() > 0) {
|
||||
stanza.append(authenticationText);
|
||||
}
|
||||
stanza.append("</auth>");
|
||||
return stanza.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SASL challenge stanza.
|
||||
*/
|
||||
public static class Challenge extends Packet {
|
||||
final private String data;
|
||||
|
||||
public Challenge(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String toXML() {
|
||||
StringBuilder stanza = new StringBuilder();
|
||||
stanza.append("<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
|
||||
if (data != null &&
|
||||
data.trim().length() > 0) {
|
||||
stanza.append(data);
|
||||
}
|
||||
stanza.append("</challenge>");
|
||||
return stanza.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SASL response stanza.
|
||||
*/
|
||||
public static class Response extends Packet {
|
||||
final private String authenticationText;
|
||||
|
||||
public Response() {
|
||||
authenticationText = null;
|
||||
}
|
||||
|
||||
public Response(String authenticationText) {
|
||||
if (authenticationText == null || authenticationText.trim().length() == 0) {
|
||||
this.authenticationText = null;
|
||||
}
|
||||
else {
|
||||
this.authenticationText = authenticationText;
|
||||
}
|
||||
}
|
||||
|
||||
public String toXML() {
|
||||
StringBuilder stanza = new StringBuilder();
|
||||
stanza.append("<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
|
||||
if (authenticationText != null) {
|
||||
stanza.append(authenticationText);
|
||||
}
|
||||
stanza.append("</response>");
|
||||
return stanza.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SASL success stanza.
|
||||
*/
|
||||
public static class Success extends Packet {
|
||||
final private String data;
|
||||
|
||||
public Success(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String toXML() {
|
||||
StringBuilder stanza = new StringBuilder();
|
||||
stanza.append("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
|
||||
if (data != null &&
|
||||
data.trim().length() > 0) {
|
||||
stanza.append(data);
|
||||
}
|
||||
stanza.append("</success>");
|
||||
return stanza.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SASL failure stanza.
|
||||
*/
|
||||
public static class SASLFailure extends Packet {
|
||||
private final SASLError saslError;
|
||||
private final String saslErrorString;
|
||||
|
||||
public SASLFailure(String saslError) {
|
||||
SASLError error = SASLError.fromString(saslError);
|
||||
if (error == null) {
|
||||
// RFC6120 6.5 states that unknown condition must be treat as generic authentication failure.
|
||||
this.saslError = SASLError.not_authorized;
|
||||
} else {
|
||||
this.saslError = error;
|
||||
}
|
||||
this.saslErrorString = saslError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SASL related error condition.
|
||||
*
|
||||
* @return the SASL related error condition.
|
||||
*/
|
||||
public SASLError getSASLError() {
|
||||
return saslError;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the SASL error as String
|
||||
*/
|
||||
public String getSASLErrorString() {
|
||||
return saslErrorString;
|
||||
}
|
||||
|
||||
public String toXML() {
|
||||
StringBuilder stanza = new StringBuilder();
|
||||
stanza.append("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
|
||||
stanza.append("<").append(saslErrorString).append("/>");
|
||||
stanza.append("</failure>");
|
||||
return stanza.toString();
|
||||
}
|
||||
}
|
||||
protected abstract SASLMechanism newInstance();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 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.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.sasl.SASLError;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class SaslStanzas {
|
||||
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
|
||||
|
||||
/**
|
||||
* Initiating SASL authentication by select a mechanism.
|
||||
*/
|
||||
public static class AuthMechanism extends Packet {
|
||||
public static final String ELEMENT = "auth";
|
||||
|
||||
private final String mechanism;
|
||||
private final String authenticationText;
|
||||
|
||||
public AuthMechanism(String mechanism, String authenticationText) {
|
||||
if (mechanism == null) {
|
||||
throw new NullPointerException("SASL mechanism shouldn't be null.");
|
||||
}
|
||||
this.mechanism = mechanism;
|
||||
this.authenticationText = StringUtils.returnIfNotEmptyTrimmed(authenticationText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).attribute("mechanism", mechanism).rightAngelBracket();
|
||||
xml.optAppend(authenticationText);
|
||||
xml.closeElement(ELEMENT);
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SASL challenge stanza.
|
||||
*/
|
||||
public static class Challenge extends Packet {
|
||||
public static final String ELEMENT = "challenge";
|
||||
|
||||
private final String data;
|
||||
|
||||
public Challenge(String data) {
|
||||
this.data = StringUtils.returnIfNotEmptyTrimmed(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder().halfOpenElement(ELEMENT).xmlnsAttribute(
|
||||
NAMESPACE).rightAngelBracket();
|
||||
xml.optAppend(data);
|
||||
xml.closeElement(ELEMENT);
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SASL response stanza.
|
||||
*/
|
||||
public static class Response extends Packet {
|
||||
public static final String ELEMENT = "response";
|
||||
|
||||
private final String authenticationText;
|
||||
|
||||
public Response() {
|
||||
authenticationText = null;
|
||||
}
|
||||
|
||||
public Response(String authenticationText) {
|
||||
this.authenticationText = StringUtils.returnIfNotEmptyTrimmed(authenticationText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngelBracket();
|
||||
xml.optAppend(authenticationText);
|
||||
xml.closeElement(ELEMENT);
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SASL success stanza.
|
||||
*/
|
||||
public static class Success extends Packet {
|
||||
public static final String ELEMENT = "success";
|
||||
|
||||
final private String data;
|
||||
|
||||
/**
|
||||
* Construct a new SASL success stanza with optional additional data for the SASL layer
|
||||
* (RFC6120 6.3.10)
|
||||
*
|
||||
* @param data additional data for the SASL layer or <code>null</code>
|
||||
*/
|
||||
public Success(String data) {
|
||||
this.data = StringUtils.returnIfNotEmptyTrimmed(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional data for the SASL layer or <code>null</code>.
|
||||
*
|
||||
* @return additional data or <code>null</code>
|
||||
*/
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngelBracket();
|
||||
xml.optAppend(data);
|
||||
xml.closeElement(ELEMENT);
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SASL failure stanza.
|
||||
*/
|
||||
public static class SASLFailure extends Packet {
|
||||
public static final String ELEMENT = "failure";
|
||||
|
||||
private final SASLError saslError;
|
||||
private final String saslErrorString;
|
||||
|
||||
public SASLFailure(String saslError) {
|
||||
SASLError error = SASLError.fromString(saslError);
|
||||
if (error == null) {
|
||||
// RFC6120 6.5 states that unknown condition must be treat as generic authentication
|
||||
// failure.
|
||||
this.saslError = SASLError.not_authorized;
|
||||
}
|
||||
else {
|
||||
this.saslError = error;
|
||||
}
|
||||
this.saslErrorString = saslError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SASL related error condition.
|
||||
*
|
||||
* @return the SASL related error condition.
|
||||
*/
|
||||
public SASLError getSASLError() {
|
||||
return saslError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SASL error as String
|
||||
*/
|
||||
public String getSASLErrorString() {
|
||||
return saslErrorString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
xml.halfOpenElement(ELEMENT).xmlnsAttribute(ELEMENT).rightAngelBracket();
|
||||
xml.emptyElement(saslErrorString);
|
||||
xml.closeElement(ELEMENT);
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 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.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class ByteUtils {
|
||||
|
||||
private static MessageDigest md5Digest;
|
||||
|
||||
public synchronized static byte[] md5(byte[] data) {
|
||||
if (md5Digest == null) {
|
||||
try {
|
||||
md5Digest = MessageDigest.getInstance(StringUtils.MD5);
|
||||
}
|
||||
catch (NoSuchAlgorithmException nsae) {
|
||||
// Smack wont be able to function normally if this exception is thrown, wrap it into
|
||||
// an ISE and make the user aware of the problem.
|
||||
throw new IllegalStateException(nsae);
|
||||
}
|
||||
}
|
||||
md5Digest.update(data);
|
||||
return md5Digest.digest();
|
||||
}
|
||||
|
||||
public static byte[] concact(byte[] arrayOne, byte[] arrayTwo) {
|
||||
int combinedLength = arrayOne.length + arrayTwo.length;
|
||||
byte[] res = new byte[combinedLength];
|
||||
System.arraycopy(arrayOne, 0, res, 0, arrayOne.length);
|
||||
System.arraycopy(arrayTwo, 0, res, arrayOne.length, arrayTwo.length);
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ import org.jivesoftware.smack.packet.XMPPError;
|
|||
import org.jivesoftware.smack.provider.IQProvider;
|
||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||
import org.jivesoftware.smack.provider.ProviderManager;
|
||||
import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Random;
|
|||
*/
|
||||
public class StringUtils {
|
||||
|
||||
public static final String MD5 = "MD5";
|
||||
public static final String SHA1 = "SHA-1";
|
||||
public static final String UTF8 = "UTF-8";
|
||||
|
||||
|
@ -37,6 +38,8 @@ public class StringUtils {
|
|||
public static final String LT_ENCODE = "<";
|
||||
public static final String GT_ENCODE = ">";
|
||||
|
||||
public static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
|
||||
|
||||
/**
|
||||
* Escapes all necessary characters in the String so that it can be used
|
||||
* in an XML doc.
|
||||
|
@ -129,14 +132,7 @@ public class StringUtils {
|
|||
}
|
||||
}
|
||||
// Now, compute hash.
|
||||
try {
|
||||
digest.update(data.getBytes(UTF8));
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
// Smack wont be able to function normally if this exception is thrown, wrap it into an
|
||||
// ISE and make the user aware of the problem.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
digest.update(toBytes(data));
|
||||
return encodeHex(digest.digest());
|
||||
}
|
||||
|
||||
|
@ -147,16 +143,22 @@ public class StringUtils {
|
|||
* @return generated hex string.
|
||||
*/
|
||||
public static String encodeHex(byte[] bytes) {
|
||||
StringBuilder hex = new StringBuilder(bytes.length * 2);
|
||||
|
||||
for (byte aByte : bytes) {
|
||||
if (((int) aByte & 0xff) < 0x10) {
|
||||
hex.append("0");
|
||||
}
|
||||
hex.append(Integer.toString((int) aByte & 0xff, 16));
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for ( int j = 0; j < bytes.length; j++ ) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_CHARS[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_CHARS[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
return hex.toString();
|
||||
public static byte[] toBytes(String string) {
|
||||
try {
|
||||
return string.getBytes(StringUtils.UTF8);
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("UTF-8 encoding not supported by platform", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,13 +168,7 @@ public class StringUtils {
|
|||
* @return a base64 encoded String.
|
||||
*/
|
||||
public static String encodeBase64(String data) {
|
||||
byte [] bytes = null;
|
||||
try {
|
||||
bytes = data.getBytes("ISO-8859-1");
|
||||
}
|
||||
catch (UnsupportedEncodingException uee) {
|
||||
throw new IllegalStateException(uee);
|
||||
}
|
||||
byte [] bytes = toBytes(data);
|
||||
return encodeBase64(bytes);
|
||||
}
|
||||
|
||||
|
@ -218,17 +214,14 @@ public class StringUtils {
|
|||
* @return the decoded String.
|
||||
*/
|
||||
public static byte[] decodeBase64(String data) {
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = data.getBytes("UTF-8");
|
||||
} catch (java.io.UnsupportedEncodingException uee) {
|
||||
bytes = data.getBytes();
|
||||
}
|
||||
|
||||
bytes = Base64.decode(bytes, 0, bytes.length, Base64.NO_OPTIONS);
|
||||
return bytes;
|
||||
byte[] bytes = toBytes(data);
|
||||
return decodeBase64(bytes);
|
||||
}
|
||||
|
||||
public static byte[] decodeBase64(byte[] data) {
|
||||
return Base64.decode(data, 0, data.length, Base64.NO_OPTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pseudo-random number generator object for use with randomString().
|
||||
* The Random class is not considered to be cryptographically secure, so
|
||||
|
@ -306,4 +299,15 @@ public class StringUtils {
|
|||
res = res.substring(0, res.length() - 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static String returnIfNotEmptyTrimmed(String string) {
|
||||
if (string == null)
|
||||
return null;
|
||||
String trimmedString = string.trim();
|
||||
if (trimmedString.length() > 0) {
|
||||
return trimmedString;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,7 @@
|
|||
<className>org.jivesoftware.smack.initializer.extensions.ExtensionsInitializer</className>
|
||||
<className>org.jivesoftware.smack.initializer.experimental.ExperimentalInitializer</className>
|
||||
<className>org.jivesoftware.smack.initializer.legacy.LegacyInitializer</className>
|
||||
<className>org.jivesoftware.smack.sasl.javax.SASLJavaXSmackInitializer</className>
|
||||
<className>org.jivesoftware.smack.sasl.provided.SASLProvidedSmackInitializer</className>
|
||||
</optionalStartupClasses>
|
||||
</smack>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
* Copyright © 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.
|
||||
|
@ -16,20 +16,16 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.sasl;
|
||||
|
||||
import org.jivesoftware.smack.SASLAuthentication;
|
||||
import org.jivesoftware.smack.DummyConnection;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
|
||||
/**
|
||||
* Implementation of the SASL PLAIN mechanism
|
||||
*
|
||||
* @author Jay Kline
|
||||
*/
|
||||
public class SASLPlainMechanism extends SASLMechanism {
|
||||
public class AbstractSaslTest {
|
||||
|
||||
public SASLPlainMechanism(SASLAuthentication saslAuthentication) {
|
||||
super(saslAuthentication);
|
||||
protected final XMPPConnection xmppConnection = new DummyConnection();
|
||||
protected final SASLMechanism saslMechanism;
|
||||
|
||||
protected AbstractSaslTest(SASLMechanism saslMechanism) {
|
||||
this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection);
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return "PLAIN";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
public class DigestMd5SaslTest extends AbstractSaslTest {
|
||||
|
||||
protected static final String challenge = "realm=\"xmpp.org\",nonce=\"aTUr3GXqUtyy2B7HVDW6C+gQs+j+0EhWWjoBKkkg\",qop=\"auth\",charset=utf-8,algorithm=md5-sess";
|
||||
protected static final byte[] challengeBytes = StringUtils.toBytes(challenge);
|
||||
|
||||
public DigestMd5SaslTest(SASLMechanism saslMechanism) {
|
||||
super(saslMechanism);
|
||||
}
|
||||
|
||||
protected void runTest() throws NotConnectedException, SmackException {
|
||||
saslMechanism.authenticate("florian", "irrelevant", "xmpp.org", "secret");
|
||||
|
||||
byte[] response = saslMechanism.evaluateChallenge(challengeBytes);
|
||||
String responseString = new String(response);
|
||||
String[] responseParts = responseString.split(",");
|
||||
Map<String, String> responsePairs = new HashMap<String, String>();
|
||||
for (String part : responseParts) {
|
||||
String[] keyValue = part.split("=");
|
||||
assertTrue(keyValue.length == 2);
|
||||
String key = keyValue[0];
|
||||
String value = keyValue[1].replace("\"", "");
|
||||
responsePairs.put(key, value);
|
||||
}
|
||||
assertMapValue("username", "florian", responsePairs);
|
||||
assertMapValue("realm", "xmpp.org", responsePairs);
|
||||
assertMapValue("digest-uri", "xmpp/xmpp.org", responsePairs);
|
||||
assertMapValue("qop", "auth", responsePairs);
|
||||
}
|
||||
|
||||
private static void assertMapValue(String key, String value, Map<String, String> map) {
|
||||
assertEquals(map.get(key), value);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue