mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-25 13:32:07 +01: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
|
@ -1 +1,10 @@
|
||||||
language: java
|
language: java
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- sudo apt-get update -qq
|
||||||
|
- if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch; fi
|
||||||
|
- wget http://dl.google.com/android/android-sdk_r23.0.2-linux.tgz
|
||||||
|
- tar xzf android-*
|
||||||
|
- export ANDROID_HOME=$PWD/android-sdk-linux
|
||||||
|
- export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
||||||
|
- echo "y" | android update sdk --filter platform-tools,android-8 --no-ui --force
|
||||||
|
|
|
@ -237,7 +237,7 @@ subprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
['smack-resolver-javax'].each { name ->
|
['smack-resolver-javax', 'smack-sasl-javax', 'smack-sasl-provided'].each { name ->
|
||||||
project(":$name") {
|
project(":$name") {
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
|
|
|
@ -6,8 +6,11 @@ include 'smack-core',
|
||||||
'smack-resolver-dnsjava',
|
'smack-resolver-dnsjava',
|
||||||
'smack-resolver-minidns',
|
'smack-resolver-minidns',
|
||||||
'smack-resolver-javax',
|
'smack-resolver-javax',
|
||||||
|
'smack-sasl-javax',
|
||||||
|
'smack-sasl-provided',
|
||||||
'smack-compression-jzlib',
|
'smack-compression-jzlib',
|
||||||
'smack-legacy',
|
'smack-legacy',
|
||||||
'smack-jingle',
|
'smack-jingle',
|
||||||
'smack-bosh',
|
'smack-bosh',
|
||||||
|
'smack-android',
|
||||||
'smack-java7'
|
'smack-java7'
|
50
smack-android/build.gradle
Normal file
50
smack-android/build.gradle
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
description = """\
|
||||||
|
Smack for Android.
|
||||||
|
All the required dependencies to run Smack on Android"""
|
||||||
|
|
||||||
|
ext {
|
||||||
|
smackMinAndroidSdk = 8
|
||||||
|
androidProjects = [':smack-tcp',':smack-core', ':smack-resolver-minidns', ':smack-sasl-provided', ':smack-extensions', ':smack-experimental'].collect{ project(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that the test dependencies (junit, …) are inferred from the
|
||||||
|
// sourceSet.test of the core subproject
|
||||||
|
dependencies {
|
||||||
|
androidProjects.each { project ->
|
||||||
|
compile project
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAndroidRuntimeJar() {
|
||||||
|
def androidHome = new File("$System.env.ANDROID_HOME")
|
||||||
|
if (!androidHome.isDirectory()) throw new Exception("ANDROID_HOME not found or set")
|
||||||
|
new File("$androidHome/platforms/android-$smackMinAndroidSdk/android.jar")
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAndroidJavadocOffline() {
|
||||||
|
def androidHome = new File("$System.env.ANDROID_HOME")
|
||||||
|
if (!androidHome.isDirectory()) throw new Exception("ANDROID_HOME not found or set")
|
||||||
|
return "$System.env.ANDROID_HOME" + "/docs/reference"
|
||||||
|
}
|
||||||
|
|
||||||
|
compileJava {
|
||||||
|
options.bootClasspath = getAndroidRuntimeJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
// See http://stackoverflow.com/a/2823592/194894
|
||||||
|
// TODO this doesn't seem to work right now. But on the other hand it
|
||||||
|
// is not really required, just to avoid a javadoc compiler warning
|
||||||
|
javadoc {
|
||||||
|
options.linksOffline "http://developer.android.com/reference", getAndroidJavadocOffline()
|
||||||
|
}
|
||||||
|
|
||||||
|
configure (androidProjects) {
|
||||||
|
task compileAndroid(type: JavaCompile) {
|
||||||
|
source = compileJava.source
|
||||||
|
classpath = compileJava.classpath
|
||||||
|
destinationDir = new File(buildDir, 'android')
|
||||||
|
options.bootClasspath = getAndroidRuntimeJar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test { dependsOn androidProjects*.compileAndroid }
|
|
@ -0,0 +1,174 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.smackx.debugger.android;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.debugger.SmackDebugger;
|
||||||
|
import org.jivesoftware.smack.AbstractConnectionListener;
|
||||||
|
import org.jivesoftware.smack.ConnectionListener;
|
||||||
|
import org.jivesoftware.smack.PacketListener;
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.util.*;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very simple debugger that prints to the android log the sent and received stanzas. Use
|
||||||
|
* this debugger with caution since printing to the console is an expensive operation that may
|
||||||
|
* even block the thread since only one thread may print at a time.<p>
|
||||||
|
* <p/>
|
||||||
|
* It is possible to not only print the raw sent and received stanzas but also the interpreted
|
||||||
|
* packets by Smack. By default interpreted packets won't be printed. To enable this feature
|
||||||
|
* just change the <tt>printInterpreted</tt> static variable to <tt>true</tt>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class AndroidDebugger implements SmackDebugger {
|
||||||
|
|
||||||
|
public static boolean printInterpreted = false;
|
||||||
|
|
||||||
|
private XMPPConnection connection = null;
|
||||||
|
|
||||||
|
private PacketListener listener = null;
|
||||||
|
private ConnectionListener connListener = null;
|
||||||
|
|
||||||
|
private Writer writer;
|
||||||
|
private Reader reader;
|
||||||
|
private ReaderListener readerListener;
|
||||||
|
private WriterListener writerListener;
|
||||||
|
|
||||||
|
public AndroidDebugger(XMPPConnection connection, Writer writer, Reader reader) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.writer = writer;
|
||||||
|
this.reader = reader;
|
||||||
|
createDebug();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the listeners that will print in the console when new activity is detected.
|
||||||
|
*/
|
||||||
|
private void createDebug() {
|
||||||
|
// 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) {
|
||||||
|
Log.d("SMACK", "RCV (" + connection.getConnectionCounter() +
|
||||||
|
"): " + 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) {
|
||||||
|
Log.d("SMACK", "SENT (" + connection.getConnectionCounter() +
|
||||||
|
"): " + str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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) {
|
||||||
|
if (printInterpreted) {
|
||||||
|
Log.d("SMACK", "RCV PKT (" + connection.getConnectionCounter() +
|
||||||
|
"): " + packet.toXML());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
connListener = new AbstractConnectionListener() {
|
||||||
|
public void connectionClosed() {
|
||||||
|
Log.d("SMACK", "Connection closed (" + connection.getConnectionCounter() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connectionClosedOnError(Exception e) {
|
||||||
|
Log.d("SMACK", "Connection closed due to an exception (" +
|
||||||
|
connection.getConnectionCounter() + ")");
|
||||||
|
}
|
||||||
|
public void reconnectionFailed(Exception e) {
|
||||||
|
Log.d("SMACK", "Reconnection failed due to an exception (" +
|
||||||
|
connection.getConnectionCounter() + ")");
|
||||||
|
}
|
||||||
|
public void reconnectionSuccessful() {
|
||||||
|
Log.d("SMACK", "Connection reconnected (" +
|
||||||
|
connection.getConnectionCounter() + ")");
|
||||||
|
}
|
||||||
|
public void reconnectingIn(int seconds) {
|
||||||
|
Log.d("SMACK", "Connection (" + connection.getConnectionCounter() +
|
||||||
|
") will reconnect in " + seconds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
String title =
|
||||||
|
"User logged (" + connection.getConnectionCounter() + "): "
|
||||||
|
+ user
|
||||||
|
+ "@"
|
||||||
|
+ connection.getServiceName()
|
||||||
|
+ ":"
|
||||||
|
+ connection.getPort();
|
||||||
|
Log.d("SMACK", title);
|
||||||
|
// Add the connection listener to the connection so that the debugger can be notified
|
||||||
|
// whenever the connection is closed.
|
||||||
|
connection.addConnectionListener(connListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reader getReader() {
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Writer getWriter() {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketListener getReaderListener() {
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketListener getWriterListener() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@ package org.jivesoftware.smack.bosh;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
|
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Challenge;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism.Success;
|
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
||||||
import org.xmlpull.v1.XmlPullParserFactory;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
@ -97,8 +97,9 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
||||||
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"),
|
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"),
|
||||||
connection.getServiceName())
|
connection.getServiceName())
|
||||||
.build());
|
.build());
|
||||||
connection.getSASLAuthentication().authenticated();
|
Success success = new Success(parser.nextText());
|
||||||
connection.processPacket(new Success(parser.nextText()));
|
connection.getSASLAuthentication().authenticated(success);
|
||||||
|
connection.processPacket(success);
|
||||||
} else if (parser.getName().equals("features")) {
|
} else if (parser.getName().equals("features")) {
|
||||||
parseFeatures(parser);
|
parseFeatures(parser);
|
||||||
} else if (parser.getName().equals("failure")) {
|
} else if (parser.getName().equals("failure")) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ Smack core components."""
|
||||||
configurations {
|
configurations {
|
||||||
compression
|
compression
|
||||||
dns
|
dns
|
||||||
|
sasl
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -34,9 +35,23 @@ task dnsJar(type: Jar) {
|
||||||
include('org/jivesoftware/smack/initializer/**')
|
include('org/jivesoftware/smack/initializer/**')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task saslJar(type: Jar) {
|
||||||
|
appendix = 'sasl'
|
||||||
|
dependsOn classes
|
||||||
|
from sourceSets.main.output
|
||||||
|
include('org/jivesoftware/smack/SASLAuthentication.class')
|
||||||
|
include('org/jivesoftware/smack/SmackException.class')
|
||||||
|
include('org/jivesoftware/smack/XMPPConnection.class')
|
||||||
|
include('org/jivesoftware/smack/sasl/**')
|
||||||
|
include('org/jivesoftware/smack/initializer/**')
|
||||||
|
include('org/jivesoftware/smack/util/StringUtils.class')
|
||||||
|
include('org/jivesoftware/smack/util/ByteUtils.class')
|
||||||
|
}
|
||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
compression compressionJar
|
compression compressionJar
|
||||||
dns dnsJar
|
dns dnsJar
|
||||||
|
sasl saslJar
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateFileTask extends DefaultTask {
|
class CreateFileTask extends DefaultTask {
|
||||||
|
|
|
@ -33,8 +33,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.security.sasl.SaslException;
|
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||||
|
@ -60,7 +58,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
private static final String[] DEBUGGERS = new String[] {
|
private static final String[] DEBUGGERS = new String[] {
|
||||||
"org.jivesoftware.smackx.debugger.EnhancedDebugger",
|
"org.jivesoftware.smackx.debugger.EnhancedDebugger",
|
||||||
"org.jivesoftware.smackx.debugger.android.AndroidDebugger",
|
"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.
|
* 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 XMPPException if an error occurs on the XMPP protocol level.
|
||||||
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
||||||
* @throws IOException
|
* @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");
|
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 XMPPException if an error occurs on the XMPP protocol level.
|
||||||
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
||||||
* @throws IOException
|
* @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
|
* 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 XMPPException if an error occurs on the XMPP protocol level.
|
||||||
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
* @throws SmackException if an error occurs somehwere else besides XMPP protocol level.
|
||||||
* @throws IOException
|
* @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
|
* Notification message saying that the server requires the client to bind a
|
||||||
|
|
|
@ -18,54 +18,37 @@
|
||||||
package org.jivesoftware.smack;
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
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.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
|
||||||
import org.jivesoftware.smack.sasl.SASLAnonymous;
|
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.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;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||||
import org.jivesoftware.smack.sasl.SASLPlainMechanism;
|
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
||||||
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
import javax.security.sasl.SaslException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>This class is responsible authenticating the user using SASL, binding the resource
|
* <p>This class is responsible authenticating the user using SASL, binding the resource
|
||||||
* to the connection and establishing a session with the server.</p>
|
* 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
|
* <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
|
* register with the server or authenticate using SASL. If the
|
||||||
* server supports SASL then Smack will first try to authenticate using SASL. But if that
|
* server supports SASL then Smack will try to authenticate using SASL..</p>
|
||||||
* fails then Non-SASL will be tried.</p>
|
|
||||||
*
|
*
|
||||||
* <p>The server may support many SASL mechanisms to use for authenticating. Out of the box
|
* <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
|
* 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
|
* {@link #registerSASLMechanism(SASLMechanism)} to register a new mechanisms.
|
||||||
* 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>
|
|
||||||
*
|
*
|
||||||
* @see org.jivesoftware.smack.sasl.SASLMechanism
|
* @see org.jivesoftware.smack.sasl.SASLMechanism
|
||||||
*
|
*
|
||||||
|
@ -74,114 +57,91 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class SASLAuthentication {
|
public class SASLAuthentication {
|
||||||
|
|
||||||
private static Map<String, Class<? extends SASLMechanism>> implementedMechanisms = new HashMap<String, Class<? extends SASLMechanism>>();
|
private static final List<SASLMechanism> REGISTERED_MECHANISMS = new ArrayList<SASLMechanism>();
|
||||||
private static List<String> mechanismsPreferences = new ArrayList<String>();
|
|
||||||
|
|
||||||
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 Collection<String> serverMechanisms = new ArrayList<String>();
|
||||||
private SASLMechanism currentMechanism = null;
|
private SASLMechanism currentMechanism = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean indicating if SASL negotiation has finished and was successful.
|
* Boolean indicating if SASL negotiation has finished and was successful.
|
||||||
*/
|
*/
|
||||||
private boolean saslNegotiated;
|
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 {
|
SASLAuthentication(AbstractXMPPConnection connection) {
|
||||||
|
|
||||||
// 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();
|
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
@ -216,42 +176,16 @@ public class SASLAuthentication {
|
||||||
* @param cbh the CallbackHandler used to get information from the user
|
* @param cbh the CallbackHandler used to get information from the user
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws XMPPErrorException
|
* @throws XMPPErrorException
|
||||||
* @throws NoResponseException
|
|
||||||
* @throws SASLErrorException
|
* @throws SASLErrorException
|
||||||
* @throws ResourceBindingNotOfferedException
|
* @throws SmackException
|
||||||
* @throws NotConnectedException
|
|
||||||
*/
|
*/
|
||||||
public void authenticate(String resource, CallbackHandler cbh) throws IOException,
|
public void authenticate(String resource, CallbackHandler cbh) throws IOException,
|
||||||
NoResponseException, XMPPErrorException, SASLErrorException, ResourceBindingNotOfferedException, NotConnectedException {
|
XMPPErrorException, SASLErrorException, SmackException {
|
||||||
// Locate the SASLMechanism to use
|
SASLMechanism selectedMechanism = selectMechanism();
|
||||||
String selectedMechanism = null;
|
|
||||||
for (String mechanism : mechanismsPreferences) {
|
|
||||||
if (implementedMechanisms.containsKey(mechanism)
|
|
||||||
&& serverMechanisms.contains(mechanism)) {
|
|
||||||
selectedMechanism = mechanism;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedMechanism != null) {
|
if (selectedMechanism != null) {
|
||||||
// A SASL mechanism was found. Authenticate using the selected mechanism and then
|
currentMechanism = selectedMechanism;
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
// Trigger SASL authentication with the selected mechanism. We use
|
currentMechanism.authenticate(connection.getHost(), connection.getServiceName(), cbh);
|
||||||
// connection.getHost() since GSAPI requires the FQDN of the server, which
|
|
||||||
// may not match the XMPP domain.
|
|
||||||
currentMechanism.authenticate(connection.getHost(), cbh);
|
|
||||||
try {
|
try {
|
||||||
// Wait until SASL negotiation finishes
|
// Wait until SASL negotiation finishes
|
||||||
wait(connection.getPacketReplyTimeout());
|
wait(connection.getPacketReplyTimeout());
|
||||||
|
@ -261,18 +195,14 @@ public class SASLAuthentication {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saslFailure != null) {
|
maybeThrowException();
|
||||||
// SASL authentication failed and the server may have closed the connection
|
|
||||||
// so throw an exception
|
|
||||||
throw new SASLErrorException(selectedMechanism, saslFailure);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!saslNegotiated) {
|
if (!saslNegotiated) {
|
||||||
throw new NoResponseException();
|
throw new NoResponseException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new SaslException(
|
throw new SmackException(
|
||||||
"SASL Authentication failed. No known authentication mechanisims.");
|
"SASL Authentication failed. No known authentication mechanisims.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,47 +221,18 @@ public class SASLAuthentication {
|
||||||
* @throws XMPPErrorException
|
* @throws XMPPErrorException
|
||||||
* @throws SASLErrorException
|
* @throws SASLErrorException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws SaslException
|
|
||||||
* @throws SmackException
|
* @throws SmackException
|
||||||
*/
|
*/
|
||||||
public void authenticate(String username, String password, String resource)
|
public void authenticate(String username, String password, String resource)
|
||||||
throws XMPPErrorException, SASLErrorException, SaslException, IOException,
|
throws XMPPErrorException, SASLErrorException, IOException,
|
||||||
SmackException {
|
SmackException {
|
||||||
// Locate the SASLMechanism to use
|
SASLMechanism selectedMechanism = selectMechanism();
|
||||||
String selectedMechanism = null;
|
|
||||||
for (String mechanism : mechanismsPreferences) {
|
|
||||||
if (implementedMechanisms.containsKey(mechanism)
|
|
||||||
&& serverMechanisms.contains(mechanism)) {
|
|
||||||
selectedMechanism = mechanism;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedMechanism != null) {
|
if (selectedMechanism != null) {
|
||||||
// A SASL mechanism was found. Authenticate using the selected mechanism and then
|
currentMechanism = selectedMechanism;
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
// Trigger SASL authentication with the selected mechanism. We use
|
currentMechanism.authenticate(username, connection.getHost(),
|
||||||
// connection.getHost() since GSAPI requires the FQDN of the server, which
|
connection.getServiceName(), password);
|
||||||
// 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);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait until SASL negotiation finishes
|
// Wait until SASL negotiation finishes
|
||||||
wait(connection.getPacketReplyTimeout());
|
wait(connection.getPacketReplyTimeout());
|
||||||
|
@ -339,21 +240,16 @@ public class SASLAuthentication {
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saslFailure != null) {
|
maybeThrowException();
|
||||||
// SASL authentication failed and the server may have closed the connection
|
|
||||||
// so throw an exception
|
|
||||||
throw new SASLErrorException(selectedMechanism, saslFailure);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!saslNegotiated) {
|
if (!saslNegotiated) {
|
||||||
throw new NoResponseException();
|
throw new NoResponseException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new SaslException(
|
throw new SmackException(
|
||||||
"SASL Authentication failed. No known authentication mechanisims.");
|
"SASL Authentication failed. No known authentication mechanisims.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,14 +263,12 @@ public class SASLAuthentication {
|
||||||
* no username.
|
* no username.
|
||||||
*
|
*
|
||||||
* @throws SASLErrorException
|
* @throws SASLErrorException
|
||||||
* @throws IOException
|
|
||||||
* @throws SaslException
|
|
||||||
* @throws XMPPErrorException if an error occures while authenticating.
|
* @throws XMPPErrorException if an error occures while authenticating.
|
||||||
* @throws SmackException if there was no response from the server.
|
* @throws SmackException if there was no response from the server.
|
||||||
*/
|
*/
|
||||||
public void authenticateAnonymously() throws SASLErrorException, SaslException, IOException,
|
public void authenticateAnonymously() throws SASLErrorException,
|
||||||
SmackException, XMPPErrorException {
|
SmackException, XMPPErrorException {
|
||||||
currentMechanism = new SASLAnonymous(this);
|
currentMechanism = (new SASLAnonymous()).instanceForAuthentication(connection);
|
||||||
|
|
||||||
// Wait until SASL negotiation finishes
|
// Wait until SASL negotiation finishes
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
@ -387,17 +281,24 @@ public class SASLAuthentication {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saslFailure != null) {
|
maybeThrowException();
|
||||||
// SASL authentication failed and the server may have closed the connection
|
|
||||||
// so throw an exception
|
|
||||||
throw new SASLErrorException(currentMechanism.toString(), saslFailure);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!saslNegotiated) {
|
if (!saslNegotiated) {
|
||||||
throw new NoResponseException();
|
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
|
* 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
|
* 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.
|
* Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set
|
||||||
|
* to <code>false</code>.
|
||||||
*
|
*
|
||||||
* @return true if the user was able to authenticate with the server usins SASL.
|
* @param challenge a base64 encoded string representing the challenge.
|
||||||
|
* @throws SmackException
|
||||||
*/
|
*/
|
||||||
public boolean isAuthenticated() {
|
public void challengeReceived(String challenge) throws SmackException {
|
||||||
return saslNegotiated;
|
challengeReceived(challenge, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server is challenging the SASL authentication we just sent. Forward the challenge
|
* 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
|
* the server. The length of the challenge-response sequence varies according to the
|
||||||
* SASLMechanism in use.
|
* SASLMechanism in use.
|
||||||
*
|
*
|
||||||
* @param challenge a base64 encoded string representing the challenge.
|
* @param challenge a base64 encoded string representing the challenge.
|
||||||
* @throws IOException If a network error occures while authenticating.
|
* @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 {
|
public void challengeReceived(String challenge, boolean finalChallenge) throws SmackException {
|
||||||
currentMechanism.challengeReceived(challenge);
|
try {
|
||||||
|
currentMechanism.challengeReceived(challenge, finalChallenge);
|
||||||
|
} catch (SmackException e) {
|
||||||
|
authenticationFailed(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification message saying that SASL authentication was successful. The next step
|
* Notification message saying that SASL authentication was successful. The next step
|
||||||
* would be to bind the resource.
|
* 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;
|
saslNegotiated = true;
|
||||||
// Wake up the thread that is waiting in the #authenticate method
|
// Wake up the thread that is waiting in the #authenticate method
|
||||||
synchronized (this) {
|
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>
|
* @see <a href="https://tools.ietf.org/html/rfc6120#section-6.5">RFC6120 6.5</a>
|
||||||
*/
|
*/
|
||||||
public void authenticationFailed(SASLFailure saslFailure) {
|
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
|
// Wake up the thread that is waiting in the #authenticate method
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
notify();
|
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
|
* 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
|
* 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() {
|
protected void init() {
|
||||||
saslNegotiated = false;
|
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)) {
|
if (SmackInitializer.class.isAssignableFrom(initClass)) {
|
||||||
SmackInitializer initializer = (SmackInitializer) initClass.newInstance();
|
SmackInitializer initializer = (SmackInitializer) initClass.newInstance();
|
||||||
List<Exception> exceptions = initializer.initialize();
|
List<Exception> exceptions = initializer.initialize();
|
||||||
if (exceptions.size() == 0) {
|
if (exceptions == null || exceptions.size() == 0) {
|
||||||
LOGGER.log(Level.FINE, "Loaded SmackInitializer " + className);
|
LOGGER.log(Level.FINE, "Loaded SmackInitializer " + className);
|
||||||
} else {
|
} else {
|
||||||
for (Exception e : exceptions) {
|
for (Exception e : exceptions) {
|
||||||
|
|
|
@ -16,10 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl;
|
package org.jivesoftware.smack.sasl;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SASLAuthentication;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
|
||||||
|
@ -30,32 +27,30 @@ import javax.security.auth.callback.CallbackHandler;
|
||||||
*/
|
*/
|
||||||
public class SASLAnonymous extends SASLMechanism {
|
public class SASLAnonymous extends SASLMechanism {
|
||||||
|
|
||||||
public SASLAnonymous(SASLAuthentication saslAuthentication) {
|
public String getName() {
|
||||||
super(saslAuthentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getName() {
|
|
||||||
return "ANONYMOUS";
|
return "ANONYMOUS";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, NotConnectedException {
|
@Override
|
||||||
authenticate();
|
public int getPriority() {
|
||||||
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void authenticate(String username, String host, String password) throws IOException, NotConnectedException {
|
@Override
|
||||||
authenticate();
|
protected void authenticateInternal(CallbackHandler cbh)
|
||||||
|
throws SmackException {
|
||||||
|
// Nothing to do here
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void authenticate() throws IOException, NotConnectedException {
|
@Override
|
||||||
// Send the authentication to the server
|
protected String getAuthenticationText() throws SmackException {
|
||||||
getSASLAuthentication().send(new AuthMechanism(getName(), null));
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void challengeReceived(String challenge) throws IOException, NotConnectedException {
|
@Override
|
||||||
// Build the challenge response stanza encoding the response text
|
public SASLAnonymous newInstance() {
|
||||||
// and send the authentication to the server
|
return new SASLAnonymous();
|
||||||
getSASLAuthentication().send(new Response());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jivesoftware.smack.XMPPException;
|
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 {
|
public class SASLErrorException extends XMPPException {
|
||||||
|
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,25 +16,14 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl;
|
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.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 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.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:
|
* 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:
|
* Subclasses will likely want to implement their own versions of these methods:
|
||||||
* <li>{@link #authenticate(String, String, String, String)} -- Initiate authentication stanza using the
|
* <li>{@link #authenticate(String, String, String, String)} -- Initiate authentication stanza using the
|
||||||
* deprecated method.</li>
|
* 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>
|
* 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>
|
* </ul>
|
||||||
*
|
*
|
||||||
* Basic XMPP SASL authentication steps:
|
* Basic XMPP SASL authentication steps:
|
||||||
|
@ -73,26 +62,40 @@ import javax.security.sasl.SaslException;
|
||||||
*
|
*
|
||||||
* @author Jay Kline
|
* @author Jay Kline
|
||||||
*/
|
*/
|
||||||
public abstract class SASLMechanism implements CallbackHandler {
|
public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
|
|
||||||
private SASLAuthentication saslAuthentication;
|
public static final String CRAMMD5 = "CRAM-MD5";
|
||||||
protected SaslClient sc;
|
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 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
|
* 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
|
* authentication is not recommended, since it is very inflexible. Use
|
||||||
* {@link #authenticate(String, CallbackHandler)} whenever possible.
|
* {@link #authenticate(String, String, CallbackHandler)} whenever possible.
|
||||||
*
|
*
|
||||||
* Explanation of auth stanza:
|
* 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:
|
* From RFC-2831:
|
||||||
* digest-uri = "digest-uri" "=" digest-uri-value
|
* digest-uri = "digest-uri" "=" digest-uri-value
|
||||||
* digest-uri-value = serv-type "/" host [ "/" serv-name ]
|
* 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
|
* @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
|
* serviceName format is: host [ "/" serv-name ] as per RFC-2831
|
||||||
* @param password the password for this account.
|
* @param password the password for this account.
|
||||||
* @throws IOException If a network error occurs while authenticating.
|
* @throws SmackException If a network error occurs while authenticating.
|
||||||
* @throws SaslException
|
|
||||||
* @throws NotConnectedException
|
* @throws NotConnectedException
|
||||||
*/
|
*/
|
||||||
public void authenticate(String username, String host, String serviceName, String password) throws IOException, SaslException, NotConnectedException {
|
public final void authenticate(String username, String host, String serviceName, String password)
|
||||||
//Since we were not provided with a CallbackHandler, we will use our own with the given
|
throws SmackException, NotConnectedException {
|
||||||
//information
|
|
||||||
|
|
||||||
//Set the authenticationID as the username, since they must be the same in this case.
|
|
||||||
this.authenticationId = username;
|
this.authenticationId = username;
|
||||||
|
this.host = host;
|
||||||
|
this.serviceName = serviceName;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.hostname = host;
|
authenticateInternal();
|
||||||
|
|
||||||
String[] mechanisms = { getName() };
|
|
||||||
Map<String,String> props = new HashMap<String,String>();
|
|
||||||
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", serviceName, props, this);
|
|
||||||
authenticate();
|
authenticate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void authenticateInternal() throws SmackException {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds and sends the <tt>auth</tt> stanza to the server. The callback handler will handle
|
* 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.
|
* any additional information, such as the authentication ID or realm, if it is needed.
|
||||||
*
|
*
|
||||||
* @param host the hostname where the user account resides.
|
* @param host the hostname where the user account resides.
|
||||||
|
* @param serviceName the xmpp service location
|
||||||
* @param cbh the CallbackHandler to obtain user information.
|
* @param cbh the CallbackHandler to obtain user information.
|
||||||
* @throws IOException If a network error occures while authenticating.
|
* @throws SmackException
|
||||||
* @throws SaslException If a protocol error occurs or the user is not authenticated.
|
|
||||||
* @throws NotConnectedException
|
* @throws NotConnectedException
|
||||||
*/
|
*/
|
||||||
public void authenticate(String host, CallbackHandler cbh) throws IOException, SaslException, NotConnectedException {
|
public void authenticate(String host,String serviceName, CallbackHandler cbh)
|
||||||
String[] mechanisms = { getName() };
|
throws SmackException, NotConnectedException {
|
||||||
Map<String,String> props = new HashMap<String,String>();
|
this.host = host;
|
||||||
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
|
this.serviceName = serviceName;
|
||||||
|
authenticateInternal(cbh);
|
||||||
authenticate();
|
authenticate();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void authenticate() throws IOException, SaslException, NotConnectedException {
|
protected abstract void authenticateInternal(CallbackHandler cbh) throws SmackException;
|
||||||
String authenticationText = null;
|
|
||||||
if (sc.hasInitialResponse()) {
|
|
||||||
byte[] response = sc.evaluateChallenge(new byte[0]);
|
|
||||||
authenticationText = StringUtils.encodeBase64(response, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private final void authenticate() throws SmackException, NotConnectedException {
|
||||||
|
String authenticationText = getAuthenticationText();
|
||||||
// Send the authentication to the server
|
// 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
|
* The server is challenging the SASL mechanism for the stanza he just sent. Send a
|
||||||
* response to the server's challenge.
|
* response to the server's challenge.
|
||||||
*
|
*
|
||||||
* @param challenge a base64 encoded string representing the challenge.
|
* @param challengeString a base64 encoded string representing the challenge.
|
||||||
* @throws IOException if an exception sending the response occurs.
|
* @param finalChallenge true if this is the last challenge send by the server within the success stanza
|
||||||
* @throws NotConnectedException
|
* @throws NotConnectedException
|
||||||
|
* @throws SmackException
|
||||||
*/
|
*/
|
||||||
public void challengeReceived(String challenge) throws IOException, NotConnectedException {
|
public final void challengeReceived(String challengeString, boolean finalChallenge) throws SmackException, NotConnectedException {
|
||||||
byte response[];
|
byte[] challenge = StringUtils.decodeBase64(challengeString);
|
||||||
if(challenge != null) {
|
byte response[] = evaluateChallenge(challenge);
|
||||||
response = sc.evaluateChallenge(StringUtils.decodeBase64(challenge));
|
if (finalChallenge) {
|
||||||
} else {
|
return;
|
||||||
response = sc.evaluateChallenge(new byte[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Packet responseStanza;
|
Response responseStanza;
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
responseStanza = new Response();
|
responseStanza = new Response();
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,15 @@ public abstract class SASLMechanism implements CallbackHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the authentication to the server
|
// 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.
|
* @return the common name of the SASL mechanism.
|
||||||
*/
|
*/
|
||||||
protected abstract String getName();
|
public abstract String getName();
|
||||||
|
|
||||||
protected SASLAuthentication getSASLAuthentication() {
|
public abstract int getPriority();
|
||||||
return saslAuthentication;
|
|
||||||
|
public SASLMechanism instanceForAuthentication(XMPPConnection connection) {
|
||||||
|
SASLMechanism saslMechansim = newInstance();
|
||||||
|
saslMechansim.connection = connection;
|
||||||
|
return saslMechansim;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected abstract SASLMechanism newInstance();
|
||||||
*
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.IQProvider;
|
||||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||||
import org.jivesoftware.smack.provider.ProviderManager;
|
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.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlPullParserFactory;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Random;
|
||||||
*/
|
*/
|
||||||
public class StringUtils {
|
public class StringUtils {
|
||||||
|
|
||||||
|
public static final String MD5 = "MD5";
|
||||||
public static final String SHA1 = "SHA-1";
|
public static final String SHA1 = "SHA-1";
|
||||||
public static final String UTF8 = "UTF-8";
|
public static final String UTF8 = "UTF-8";
|
||||||
|
|
||||||
|
@ -37,6 +38,8 @@ public class StringUtils {
|
||||||
public static final String LT_ENCODE = "<";
|
public static final String LT_ENCODE = "<";
|
||||||
public static final String GT_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
|
* Escapes all necessary characters in the String so that it can be used
|
||||||
* in an XML doc.
|
* in an XML doc.
|
||||||
|
@ -129,14 +132,7 @@ public class StringUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Now, compute hash.
|
// Now, compute hash.
|
||||||
try {
|
digest.update(toBytes(data));
|
||||||
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);
|
|
||||||
}
|
|
||||||
return encodeHex(digest.digest());
|
return encodeHex(digest.digest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,16 +143,22 @@ public class StringUtils {
|
||||||
* @return generated hex string.
|
* @return generated hex string.
|
||||||
*/
|
*/
|
||||||
public static String encodeHex(byte[] bytes) {
|
public static String encodeHex(byte[] bytes) {
|
||||||
StringBuilder hex = new StringBuilder(bytes.length * 2);
|
char[] hexChars = new char[bytes.length * 2];
|
||||||
|
for ( int j = 0; j < bytes.length; j++ ) {
|
||||||
for (byte aByte : bytes) {
|
int v = bytes[j] & 0xFF;
|
||||||
if (((int) aByte & 0xff) < 0x10) {
|
hexChars[j * 2] = HEX_CHARS[v >>> 4];
|
||||||
hex.append("0");
|
hexChars[j * 2 + 1] = HEX_CHARS[v & 0x0F];
|
||||||
}
|
|
||||||
hex.append(Integer.toString((int) aByte & 0xff, 16));
|
|
||||||
}
|
}
|
||||||
|
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.
|
* @return a base64 encoded String.
|
||||||
*/
|
*/
|
||||||
public static String encodeBase64(String data) {
|
public static String encodeBase64(String data) {
|
||||||
byte [] bytes = null;
|
byte [] bytes = toBytes(data);
|
||||||
try {
|
|
||||||
bytes = data.getBytes("ISO-8859-1");
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException uee) {
|
|
||||||
throw new IllegalStateException(uee);
|
|
||||||
}
|
|
||||||
return encodeBase64(bytes);
|
return encodeBase64(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,15 +214,12 @@ public class StringUtils {
|
||||||
* @return the decoded String.
|
* @return the decoded String.
|
||||||
*/
|
*/
|
||||||
public static byte[] decodeBase64(String data) {
|
public static byte[] decodeBase64(String data) {
|
||||||
byte[] bytes;
|
byte[] bytes = toBytes(data);
|
||||||
try {
|
return decodeBase64(bytes);
|
||||||
bytes = data.getBytes("UTF-8");
|
}
|
||||||
} catch (java.io.UnsupportedEncodingException uee) {
|
|
||||||
bytes = data.getBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes = Base64.decode(bytes, 0, bytes.length, Base64.NO_OPTIONS);
|
public static byte[] decodeBase64(byte[] data) {
|
||||||
return bytes;
|
return Base64.decode(data, 0, data.length, Base64.NO_OPTIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -306,4 +299,15 @@ public class StringUtils {
|
||||||
res = res.substring(0, res.length() - 1);
|
res = res.substring(0, res.length() - 1);
|
||||||
return res;
|
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.extensions.ExtensionsInitializer</className>
|
||||||
<className>org.jivesoftware.smack.initializer.experimental.ExperimentalInitializer</className>
|
<className>org.jivesoftware.smack.initializer.experimental.ExperimentalInitializer</className>
|
||||||
<className>org.jivesoftware.smack.initializer.legacy.LegacyInitializer</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>
|
</optionalStartupClasses>
|
||||||
</smack>
|
</smack>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 org.jivesoftware.smack.DummyConnection;
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
|
||||||
|
public class AbstractSaslTest {
|
||||||
|
|
||||||
|
protected final XMPPConnection xmppConnection = new DummyConnection();
|
||||||
|
protected final SASLMechanism saslMechanism;
|
||||||
|
|
||||||
|
protected AbstractSaslTest(SASLMechanism saslMechanism) {
|
||||||
|
this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jivesoftware.smack.debugger;
|
package org.jivesoftware.smackx.debugger;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.datatransfer.*;
|
import java.awt.datatransfer.*;
|
||||||
|
@ -25,6 +25,7 @@ import java.io.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import org.jivesoftware.smack.*;
|
import org.jivesoftware.smack.*;
|
||||||
|
import org.jivesoftware.smack.debugger.SmackDebugger;
|
||||||
import org.jivesoftware.smack.packet.*;
|
import org.jivesoftware.smack.packet.*;
|
||||||
import org.jivesoftware.smack.util.*;
|
import org.jivesoftware.smack.util.*;
|
||||||
import org.jxmpp.util.XmppStringUtils;
|
import org.jxmpp.util.XmppStringUtils;
|
|
@ -11,6 +11,7 @@ dependencies {
|
||||||
compile project(":smack-extensions")
|
compile project(":smack-extensions")
|
||||||
compile project(":smack-tcp")
|
compile project(":smack-tcp")
|
||||||
compile project(":smack-resolver-javax")
|
compile project(":smack-resolver-javax")
|
||||||
|
compile project(":smack-sasl-javax")
|
||||||
}
|
}
|
||||||
|
|
||||||
javadoc {
|
javadoc {
|
||||||
|
|
8
smack-sasl-javax/build.gradle
Normal file
8
smack-sasl-javax/build.gradle
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
description = """\
|
||||||
|
SASL with javax.security.sasl
|
||||||
|
Use javax.security.sasl for SASL."""
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(path: ':smack-core', configuration: 'sasl')
|
||||||
|
testCompile project(':smack-core').sourceSets.test.runtimeClasspath
|
||||||
|
}
|
|
@ -14,22 +14,28 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl;
|
package org.jivesoftware.smack.sasl.javax;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SASLAuthentication;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the SASL CRAM-MD5 mechanism
|
* Implementation of the SASL CRAM-MD5 mechanism
|
||||||
*
|
*
|
||||||
* @author Jay Kline
|
* @author Jay Kline
|
||||||
*/
|
*/
|
||||||
public class SASLCramMD5Mechanism extends SASLMechanism {
|
public class SASLCramMD5Mechanism extends SASLJavaXMechanism {
|
||||||
|
|
||||||
public SASLCramMD5Mechanism(SASLAuthentication saslAuthentication) {
|
public static final String NAME = CRAMMD5;
|
||||||
super(saslAuthentication);
|
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getName() {
|
@Override
|
||||||
return "CRAM-MD5";
|
public int getPriority() {
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SASLCramMD5Mechanism newInstance() {
|
||||||
|
return new SASLCramMD5Mechanism();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,22 +14,28 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl;
|
package org.jivesoftware.smack.sasl.javax;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SASLAuthentication;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the SASL DIGEST-MD5 mechanism
|
* Implementation of the SASL DIGEST-MD5 mechanism
|
||||||
*
|
*
|
||||||
* @author Jay Kline
|
* @author Jay Kline
|
||||||
*/
|
*/
|
||||||
public class SASLDigestMD5Mechanism extends SASLMechanism {
|
public class SASLDigestMD5Mechanism extends SASLJavaXMechanism {
|
||||||
|
|
||||||
public SASLDigestMD5Mechanism(SASLAuthentication saslAuthentication) {
|
public static final String NAME = DIGESTMD5;
|
||||||
super(saslAuthentication);
|
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getName() {
|
@Override
|
||||||
return "DIGEST-MD5";
|
public int getPriority() {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SASLDigestMD5Mechanism newInstance() {
|
||||||
|
return new SASLDigestMD5Mechanism();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,9 +14,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl;
|
package org.jivesoftware.smack.sasl.javax;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SASLAuthentication;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the SASL EXTERNAL mechanism.
|
* Implementation of the SASL EXTERNAL mechanism.
|
||||||
|
@ -44,13 +42,22 @@ import org.jivesoftware.smack.SASLAuthentication;
|
||||||
*
|
*
|
||||||
* @author Jay Kline
|
* @author Jay Kline
|
||||||
*/
|
*/
|
||||||
public class SASLExternalMechanism extends SASLMechanism {
|
public class SASLExternalMechanism extends SASLJavaXMechanism {
|
||||||
|
|
||||||
public SASLExternalMechanism(SASLAuthentication saslAuthentication) {
|
public static final String NAME = EXTERNAL;
|
||||||
super(saslAuthentication);
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return EXTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getName() {
|
@Override
|
||||||
return "EXTERNAL";
|
public int getPriority() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SASLExternalMechanism newInstance() {
|
||||||
|
return new SASLExternalMechanism();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.javax;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.security.sasl.Sasl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the SASL GSSAPI mechanism
|
||||||
|
*
|
||||||
|
* @author Jay Kline
|
||||||
|
*/
|
||||||
|
public class SASLGSSAPIMechanism extends SASLJavaXMechanism {
|
||||||
|
|
||||||
|
public static final String NAME = GSSAPI;
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
|
||||||
|
System.setProperty("java.security.auth.login.config","gss.conf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, String> getSaslProps() {
|
||||||
|
Map<String, String> props = super.getSaslProps();
|
||||||
|
props.put(Sasl.SERVER_AUTH,"TRUE");
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GSSAPI differs from all other SASL mechanism such that it required the FQDN host name as
|
||||||
|
* server name and not the serviceName (At least that is what old code comments of Smack tell
|
||||||
|
* us).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String getServerName() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SASLGSSAPIMechanism newInstance() {
|
||||||
|
return new SASLGSSAPIMechanism();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.javax;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.security.auth.callback.Callback;
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
import javax.security.auth.callback.NameCallback;
|
||||||
|
import javax.security.auth.callback.PasswordCallback;
|
||||||
|
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
|
||||||
|
public abstract class SASLJavaXMechanism extends SASLMechanism {
|
||||||
|
|
||||||
|
protected SaslClient sc;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void authenticateInternal()
|
||||||
|
throws SmackException {
|
||||||
|
String[] mechanisms = { getName() };
|
||||||
|
Map<String, String> props = getSaslProps();
|
||||||
|
try {
|
||||||
|
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", getServerName(), props,
|
||||||
|
new CallbackHandler() {
|
||||||
|
@Override
|
||||||
|
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, prevents UnsupportedCallbackException
|
||||||
|
// RealmChoiceCallback rccb =
|
||||||
|
// (RealmChoiceCallback)callbacks[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UnsupportedCallbackException(callbacks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (SaslException e) {
|
||||||
|
throw new SmackException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void authenticateInternal(CallbackHandler cbh)
|
||||||
|
throws SmackException {
|
||||||
|
String[] mechanisms = { getName() };
|
||||||
|
Map<String, String> props = getSaslProps();
|
||||||
|
try {
|
||||||
|
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
|
||||||
|
}
|
||||||
|
catch (SaslException e) {
|
||||||
|
throw new SmackException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getAuthenticationText() throws SmackException {
|
||||||
|
String authenticationText = null;
|
||||||
|
if (sc.hasInitialResponse()) {
|
||||||
|
byte[] response;
|
||||||
|
try {
|
||||||
|
response = sc.evaluateChallenge(new byte[0]);
|
||||||
|
}
|
||||||
|
catch (SaslException e) {
|
||||||
|
throw new SmackException(e);
|
||||||
|
}
|
||||||
|
authenticationText = StringUtils.encodeBase64(response, false);
|
||||||
|
}
|
||||||
|
return authenticationText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
|
||||||
|
try {
|
||||||
|
if (challenge != null) {
|
||||||
|
return sc.evaluateChallenge(challenge);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return sc.evaluateChallenge(new byte[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SaslException e) {
|
||||||
|
throw new SmackException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String,String> getSaslProps() {
|
||||||
|
return new HashMap<String, String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getServerName() {
|
||||||
|
return serviceName;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.javax;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SASLAuthentication;
|
||||||
|
import org.jivesoftware.smack.initializer.SmackAndOsgiInitializer;
|
||||||
|
|
||||||
|
public class SASLJavaXSmackInitializer extends SmackAndOsgiInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Exception> initialize() {
|
||||||
|
SASLAuthentication.registerSASLMechanism(new SASLExternalMechanism());
|
||||||
|
SASLAuthentication.registerSASLMechanism(new SASLGSSAPIMechanism());
|
||||||
|
SASLAuthentication.registerSASLMechanism(new SASLDigestMD5Mechanism());
|
||||||
|
SASLAuthentication.registerSASLMechanism(new SASLCramMD5Mechanism());
|
||||||
|
SASLAuthentication.registerSASLMechanism(new SASLPlainMechanism());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Exception> initialize(ClassLoader classLoader) {
|
||||||
|
return initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,22 +14,28 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl;
|
package org.jivesoftware.smack.sasl.javax;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SASLAuthentication;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the SASL PLAIN mechanism
|
* Implementation of the SASL PLAIN mechanism
|
||||||
*
|
*
|
||||||
* @author Jay Kline
|
* @author Jay Kline
|
||||||
*/
|
*/
|
||||||
public class SASLPlainMechanism extends SASLMechanism {
|
public class SASLPlainMechanism extends SASLJavaXMechanism {
|
||||||
|
|
||||||
public SASLPlainMechanism(SASLAuthentication saslAuthentication) {
|
public static final String NAME = PLAIN;
|
||||||
super(saslAuthentication);
|
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getName() {
|
@Override
|
||||||
return "PLAIN";
|
public int getPriority() {
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SASLPlainMechanism newInstance() {
|
||||||
|
return new SASLPlainMechanism();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.2.0"
|
||||||
|
enabled="true" immediate="true" name="Smack Resolver JavaX API">
|
||||||
|
<implementation
|
||||||
|
class="org.jivesoftware.smack.sasl.javax.SASLJavaXSmackInitializer" />
|
||||||
|
</scr:component>
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.javax;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.sasl.DigestMd5SaslTest;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SASLDigestMD5Test extends DigestMd5SaslTest {
|
||||||
|
|
||||||
|
public SASLDigestMD5Test() {
|
||||||
|
super(new SASLDigestMD5Mechanism());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDigestMD5() throws NotConnectedException, SmackException {
|
||||||
|
runTest();
|
||||||
|
}
|
||||||
|
}
|
8
smack-sasl-provided/build.gradle
Normal file
8
smack-sasl-provided/build.gradle
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
description = """\
|
||||||
|
SASL with Smack provided code
|
||||||
|
Use Smack provided code for SASL."""
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(path: ':smack-core', configuration: 'sasl')
|
||||||
|
testCompile project(':smack-core').sourceSets.test.runtimeClasspath
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.provided;
|
||||||
|
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
|
import org.jivesoftware.smack.util.ByteUtils;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
|
||||||
|
public class SASLDigestMD5Mechanism extends SASLMechanism {
|
||||||
|
|
||||||
|
public static final String NAME = DIGESTMD5;
|
||||||
|
|
||||||
|
private static final String INITAL_NONCE = "00000001";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The only 'qop' value supported by this implementation
|
||||||
|
*/
|
||||||
|
private static final String QOP_VALUE = "auth";
|
||||||
|
|
||||||
|
private enum State {
|
||||||
|
INITIAL,
|
||||||
|
RESPONSE_SENT,
|
||||||
|
VALID_SERVER_RESPONSE,
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean verifyServerResponse = true;
|
||||||
|
|
||||||
|
public static void setVerifyServerResponse(boolean verifyServerResponse) {
|
||||||
|
SASLDigestMD5Mechanism.verifyServerResponse = verifyServerResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the this instance of SASL DIGEST-MD5 authentication.
|
||||||
|
*/
|
||||||
|
private State state = State.INITIAL;
|
||||||
|
|
||||||
|
private String nonce;
|
||||||
|
private String cnonce;
|
||||||
|
private String digestUri;
|
||||||
|
private String hex_hashed_a1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void authenticateInternal(CallbackHandler cbh) throws SmackException {
|
||||||
|
throw new UnsupportedOperationException("CallbackHandler not (yet) supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAuthenticationText() throws SmackException {
|
||||||
|
// DIGEST-MD5 has no initial response, return null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 210;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SASLDigestMD5Mechanism newInstance() {
|
||||||
|
return new SASLDigestMD5Mechanism();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
|
||||||
|
if (challenge.length == 0) {
|
||||||
|
throw new SmackException("Initial challenge has zero length");
|
||||||
|
}
|
||||||
|
String[] challengeParts = (new String(challenge)).split(",");
|
||||||
|
byte[] response = null;
|
||||||
|
switch (state) {
|
||||||
|
case INITIAL:
|
||||||
|
for (String part : challengeParts) {
|
||||||
|
String[] keyValue = part.split("=");
|
||||||
|
assert (keyValue.length == 2);
|
||||||
|
String key = keyValue[0];
|
||||||
|
String value = keyValue[1];
|
||||||
|
if ("nonce".equals(key)) {
|
||||||
|
if (nonce != null) {
|
||||||
|
throw new SmackException("Nonce value present multiple times");
|
||||||
|
}
|
||||||
|
nonce = value.replace("\"", "");
|
||||||
|
}
|
||||||
|
else if ("qop".equals(key)) {
|
||||||
|
value = value.replace("\"", "");
|
||||||
|
if (!value.equals("auth")) {
|
||||||
|
throw new SmackException("Unsupported qop operation: " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nonce == null) {
|
||||||
|
// RFC 2831 2.1.1 about nonce "This directive is required and MUST appear exactly
|
||||||
|
// once; if not present, or if multiple instances are present, the client should
|
||||||
|
// abort the authentication exchange."
|
||||||
|
throw new SmackException("nonce value not present in initial challenge");
|
||||||
|
}
|
||||||
|
// RFC 2831 2.1.2.1 defines A1, A2, KD and response-value
|
||||||
|
byte[] a1FirstPart = ByteUtils.md5(toBytes(authenticationId + ':' + serviceName + ':'
|
||||||
|
+ password));
|
||||||
|
cnonce = StringUtils.randomString(32);
|
||||||
|
byte[] a1 = ByteUtils.concact(a1FirstPart, toBytes(':' + nonce + ':' + cnonce));
|
||||||
|
digestUri = "xmpp/" + serviceName;
|
||||||
|
hex_hashed_a1 = StringUtils.encodeHex(ByteUtils.md5(a1));
|
||||||
|
String responseValue = calcResponse(DigestType.ClientResponse);
|
||||||
|
// @formatter:off
|
||||||
|
// See RFC 2831 2.1.2 digest-response
|
||||||
|
String saslString = "username=\"" + authenticationId + '"'
|
||||||
|
+ ",realm=\"" + serviceName + '"'
|
||||||
|
+ ",nonce=\"" + nonce + '"'
|
||||||
|
+ ",cnonce=\"" + cnonce + '"'
|
||||||
|
+ ",nc=" + INITAL_NONCE
|
||||||
|
+ ",qop=auth"
|
||||||
|
+ ",digest-uri=\"" + digestUri + '"'
|
||||||
|
+ ",response=" + responseValue
|
||||||
|
+ ",charset=utf-8";
|
||||||
|
// @formatter:on
|
||||||
|
response = toBytes(saslString);
|
||||||
|
state = State.RESPONSE_SENT;
|
||||||
|
break;
|
||||||
|
case RESPONSE_SENT:
|
||||||
|
if (verifyServerResponse) {
|
||||||
|
String serverResponse = null;
|
||||||
|
for (String part : challengeParts) {
|
||||||
|
String[] keyValue = part.split("=");
|
||||||
|
assert (keyValue.length == 2);
|
||||||
|
String key = keyValue[0];
|
||||||
|
String value = keyValue[1];
|
||||||
|
if ("rspauth".equals(key)) {
|
||||||
|
serverResponse = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (serverResponse == null) {
|
||||||
|
throw new SmackException("No server response received while performing " + NAME
|
||||||
|
+ " authentication");
|
||||||
|
}
|
||||||
|
String expectedServerResponse = calcResponse(DigestType.ServerResponse);
|
||||||
|
if (!serverResponse.equals(expectedServerResponse)) {
|
||||||
|
throw new SmackException("Invalid server response while performing " + NAME
|
||||||
|
+ " authentication");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = State.VALID_SERVER_RESPONSE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DigestType {
|
||||||
|
ClientResponse,
|
||||||
|
ServerResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
private String calcResponse(DigestType digestType) {
|
||||||
|
StringBuilder a2 = new StringBuilder();
|
||||||
|
if (digestType == DigestType.ClientResponse) {
|
||||||
|
a2.append("AUTHENTICATE");
|
||||||
|
}
|
||||||
|
a2.append(':');
|
||||||
|
a2.append(digestUri);
|
||||||
|
String hex_hashed_a2 = StringUtils.encodeHex(ByteUtils.md5(toBytes(a2.toString())));
|
||||||
|
|
||||||
|
StringBuilder kd_argument = new StringBuilder();
|
||||||
|
kd_argument.append(hex_hashed_a1);
|
||||||
|
kd_argument.append(':');
|
||||||
|
kd_argument.append(nonce);
|
||||||
|
kd_argument.append(':');
|
||||||
|
kd_argument.append(INITAL_NONCE);
|
||||||
|
kd_argument.append(':');
|
||||||
|
kd_argument.append(cnonce);
|
||||||
|
kd_argument.append(':');
|
||||||
|
kd_argument.append(QOP_VALUE);
|
||||||
|
kd_argument.append(':');
|
||||||
|
kd_argument.append(hex_hashed_a2);
|
||||||
|
byte[] kd = ByteUtils.md5(toBytes(kd_argument.toString()));
|
||||||
|
String responseValue = StringUtils.encodeHex(kd);
|
||||||
|
return responseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] toBytes(String string) {
|
||||||
|
return StringUtils.toBytes(string);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.provided;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SASLAuthentication;
|
||||||
|
import org.jivesoftware.smack.initializer.SmackAndOsgiInitializer;
|
||||||
|
|
||||||
|
public class SASLProvidedSmackInitializer extends SmackAndOsgiInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Exception> initialize() {
|
||||||
|
SASLAuthentication.registerSASLMechanism(new SASLDigestMD5Mechanism());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Exception> initialize(ClassLoader classLoader) {
|
||||||
|
return initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.2.0"
|
||||||
|
enabled="true" immediate="true" name="Smack Resolver JavaX API">
|
||||||
|
<implementation
|
||||||
|
class="org.jivesoftware.smack.sasl.provided.SASLProvidedSmackInitializer" />
|
||||||
|
</scr:component>
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.provided;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.sasl.DigestMd5SaslTest;
|
||||||
|
import org.jivesoftware.smack.sasl.provided.SASLDigestMD5Mechanism;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SASLDigestMD5Test extends DigestMd5SaslTest {
|
||||||
|
public SASLDigestMD5Test() {
|
||||||
|
super(new SASLDigestMD5Mechanism());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDigestMD5() throws NotConnectedException, SmackException {
|
||||||
|
runTest();
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,9 +35,9 @@ import org.jivesoftware.smack.packet.Packet;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
|
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
|
||||||
import org.jivesoftware.smack.parsing.UnparsablePacket;
|
import org.jivesoftware.smack.parsing.UnparsablePacket;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
|
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Challenge;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism.Success;
|
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
||||||
import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
|
import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.util.TLSUtils;
|
import org.jivesoftware.smack.util.TLSUtils;
|
||||||
|
@ -54,7 +54,6 @@ import javax.net.ssl.SSLSocket;
|
||||||
import javax.security.auth.callback.Callback;
|
import javax.security.auth.callback.Callback;
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
import javax.security.auth.callback.PasswordCallback;
|
import javax.security.auth.callback.PasswordCallback;
|
||||||
import javax.security.sasl.SaslException;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
|
@ -247,7 +246,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void login(String username, String password, String resource) throws XMPPException, SmackException, SaslException, IOException {
|
public synchronized void login(String username, String password, String resource) throws XMPPException, SmackException, IOException {
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
throw new NotConnectedException();
|
throw new NotConnectedException();
|
||||||
}
|
}
|
||||||
|
@ -266,7 +265,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
saslAuthentication.authenticate(resource, config.getCallbackHandler());
|
saslAuthentication.authenticate(resource, config.getCallbackHandler());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new SaslException("No non-anonymous SASL authentication mechanism available");
|
throw new SmackException("No non-anonymous SASL authentication mechanism available");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If compression is enabled then request the server to use stream compression. XEP-170
|
// If compression is enabled then request the server to use stream compression. XEP-170
|
||||||
|
@ -312,7 +311,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void loginAnonymously() throws XMPPException, SmackException, SaslException, IOException {
|
public synchronized void loginAnonymously() throws XMPPException, SmackException, IOException {
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
throw new NotConnectedException();
|
throw new NotConnectedException();
|
||||||
}
|
}
|
||||||
|
@ -324,7 +323,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
saslAuthentication.authenticateAnonymously();
|
saslAuthentication.authenticateAnonymously();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new SaslException("No anonymous SASL authentication mechanism available");
|
throw new SmackException("No anonymous SASL authentication mechanism available");
|
||||||
}
|
}
|
||||||
|
|
||||||
String response = bindResourceAndEstablishSession(null);
|
String response = bindResourceAndEstablishSession(null);
|
||||||
|
@ -1063,7 +1062,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
getSASLAuthentication().challengeReceived(challengeData);
|
getSASLAuthentication().challengeReceived(challengeData);
|
||||||
}
|
}
|
||||||
else if (name.equals("success")) {
|
else if (name.equals("success")) {
|
||||||
processPacket(new Success(parser.nextText()));
|
Success success = new Success(parser.nextText());
|
||||||
|
processPacket(success);
|
||||||
// We now need to bind a resource for the connection
|
// We now need to bind a resource for the connection
|
||||||
// Open a new stream and wait for the response
|
// Open a new stream and wait for the response
|
||||||
packetWriter.openStream();
|
packetWriter.openStream();
|
||||||
|
@ -1072,7 +1072,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
resetParser();
|
resetParser();
|
||||||
// The SASL authentication with the server was successful. The next step
|
// The SASL authentication with the server was successful. The next step
|
||||||
// will be to bind the resource
|
// will be to bind the resource
|
||||||
getSASLAuthentication().authenticated();
|
getSASLAuthentication().authenticated(success);
|
||||||
}
|
}
|
||||||
else if (name.equals("compressed")) {
|
else if (name.equals("compressed")) {
|
||||||
// Server confirmed that it's possible to use stream compression. Start
|
// Server confirmed that it's possible to use stream compression. Start
|
||||||
|
|
Loading…
Reference in a new issue