SASL Proxy Auth support

This adds the ability to provide a distinct authorization identifier for use
by SASL mechanisms. Not all SASL mechanisms support this operation, in
particular CRAM-MD5.

Both the javax and provided SASL implementations are extended, and an authzid
parameter added to the authenticate method.

The authorization identifier is passed as a EntityBareJid in order to assure the
correct form.

Resolves SMACK-677.

Minor-Modifications-By: Florian Schmaus <flo@geekplace.eu>
This commit is contained in:
Dave Cridland 2015-06-16 17:50:30 +01:00 committed by Florian Schmaus
parent a00331dbb4
commit 9c772add93
18 changed files with 182 additions and 32 deletions

View File

@ -219,7 +219,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException, protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
SmackException, IOException, InterruptedException { SmackException, IOException, InterruptedException {
// Authenticate using SASL // Authenticate using SASL
saslAuthentication.authenticate(username, password); saslAuthentication.authenticate(username, password, config.getAuthzid());
bindResourceAndEstablishSession(resource); bindResourceAndEstablishSession(resource);

View File

@ -31,6 +31,7 @@ import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
@ -81,6 +82,11 @@ public abstract class ConnectionConfiguration {
private final String password; private final String password;
private final Resourcepart resource; private final Resourcepart resource;
/**
* The optional SASL authorization identity (see RFC 6120 § 6.3.8).
*/
private final EntityBareJid authzid;
/** /**
* Initial presence as of RFC 6121 § 4.2 * Initial presence as of RFC 6121 § 4.2
* @see <a href="http://xmpp.org/rfcs/rfc6121.html#presence-initial">RFC 6121 § 4.2 Initial Presence</a> * @see <a href="http://xmpp.org/rfcs/rfc6121.html#presence-initial">RFC 6121 § 4.2 Initial Presence</a>
@ -110,6 +116,7 @@ public abstract class ConnectionConfiguration {
private final Set<String> enabledSaslMechanisms; private final Set<String> enabledSaslMechanisms;
protected ConnectionConfiguration(Builder<?,?> builder) { protected ConnectionConfiguration(Builder<?,?> builder) {
authzid = builder.authzid;
username = builder.username; username = builder.username;
password = builder.password; password = builder.password;
callbackHandler = builder.callbackHandler; callbackHandler = builder.callbackHandler;
@ -361,6 +368,17 @@ public abstract class ConnectionConfiguration {
return resource; return resource;
} }
/**
* Returns the optional XMPP address to be requested as the SASL authorization identity.
*
* @return the authorization identifier.
* @see <a href="http://tools.ietf.org/html/rfc6120#section-6.3.8">RFC 6120 § 6.3.8. Authorization Identity</a>
* @since 4.2
*/
public EntityBareJid getAuthzid() {
return authzid;
}
/** /**
* Returns true if an available presence should be sent when logging in while reconnecting. * Returns true if an available presence should be sent when logging in while reconnecting.
* *
@ -425,6 +443,7 @@ public abstract class ConnectionConfiguration {
private String[] enabledSSLProtocols; private String[] enabledSSLProtocols;
private String[] enabledSSLCiphers; private String[] enabledSSLCiphers;
private HostnameVerifier hostnameVerifier; private HostnameVerifier hostnameVerifier;
private EntityBareJid authzid;
private CharSequence username; private CharSequence username;
private String password; private String password;
private Resourcepart resource; private Resourcepart resource;
@ -803,6 +822,25 @@ public abstract class ConnectionConfiguration {
return getThis(); return getThis();
} }
/**
* Set the XMPP address to be used as authorization identity.
* <p>
* In XMPP, authorization identities are bare jids. In general, callers should allow the server to select the
* authorization identifier automatically, and not call this. Note that setting the authzid does not set the XMPP
* service domain, which should typically match.
* Calling this will also SASL CRAM, since this mechanism does not support authzid.
* </p>
*
* @param authzid The BareJid to be requested as the authorization identifier.
* @return a reference to this builder.
* @see <a href="http://tools.ietf.org/html/rfc6120#section-6.3.8">RFC 6120 § 6.3.8. Authorization Identity</a>
* @since 4.2
*/
public B setAuthzid(EntityBareJid authzid) {
this.authzid = authzid;
return getThis();
}
public abstract C build(); public abstract C build();
protected abstract B getThis(); protected abstract B getThis();

View File

@ -25,6 +25,7 @@ import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
@ -171,26 +172,27 @@ public final class SASLAuthentication {
* *
* @param username the username that is authenticating with the server. * @param username the username that is authenticating with the server.
* @param password the password to send to the server. * @param password the password to send to the server.
* @throws XMPPErrorException * @param authzid the authorization identifier (typically null).
* @throws SASLErrorException * @throws XMPPErrorException
* @throws IOException * @throws SASLErrorException
* @throws SmackException * @throws IOException
* @throws InterruptedException * @throws SmackException
* @throws InterruptedException
*/ */
public void authenticate(String username, String password) public void authenticate(String username, String password, EntityBareJid authzid)
throws XMPPErrorException, SASLErrorException, IOException, throws XMPPErrorException, SASLErrorException, IOException,
SmackException, InterruptedException { SmackException, InterruptedException {
currentMechanism = selectMechanism(); currentMechanism = selectMechanism(authzid);
final CallbackHandler callbackHandler = configuration.getCallbackHandler(); final CallbackHandler callbackHandler = configuration.getCallbackHandler();
final String host = connection.getHost(); final String host = connection.getHost();
final DomainBareJid xmppServiceDomain = connection.getXMPPServiceDomain(); final DomainBareJid xmppServiceDomain = connection.getXMPPServiceDomain();
synchronized (this) { synchronized (this) {
if (callbackHandler != null) { if (callbackHandler != null) {
currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler); currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid);
} }
else { else {
currentMechanism.authenticate(username, host, xmppServiceDomain, password); currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid);
} }
final long deadline = System.currentTimeMillis() + connection.getPacketReplyTimeout(); final long deadline = System.currentTimeMillis() + connection.getPacketReplyTimeout();
while (!authenticationSuccessful && saslException == null) { while (!authenticationSuccessful && saslException == null) {
@ -312,7 +314,7 @@ public final class SASLAuthentication {
return lastUsedMech.getName(); return lastUsedMech.getName();
} }
private SASLMechanism selectMechanism() throws SmackException { private SASLMechanism selectMechanism(EntityBareJid authzid) throws SmackException {
Iterator<SASLMechanism> it = REGISTERED_MECHANISMS.iterator(); Iterator<SASLMechanism> it = REGISTERED_MECHANISMS.iterator();
final List<String> serverMechanisms = getServerMechanisms(); final List<String> serverMechanisms = getServerMechanisms();
if (serverMechanisms.isEmpty()) { if (serverMechanisms.isEmpty()) {
@ -330,6 +332,12 @@ public final class SASLAuthentication {
if (!configuration.isEnabledSaslMechanism(mechanismName)) { if (!configuration.isEnabledSaslMechanism(mechanismName)) {
continue; continue;
} }
if (authzid != null) {
if (!mechanism.authzidSupported()) {
LOGGER.fine("Skipping " + mechanism + " because authzid is required by not supported by this SASL mechanism");
continue;
}
}
if (serverMechanisms.contains(mechanismName)) { if (serverMechanisms.contains(mechanismName)) {
// Create a new instance of the SASLMechanism for every authentication attempt. // Create a new instance of the SASLMechanism for every authentication attempt.
return mechanism.instanceForAuthentication(connection); return mechanism.instanceForAuthentication(connection);

View File

@ -25,6 +25,7 @@ import org.jivesoftware.smack.util.StringTransformer;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
@ -34,9 +35,9 @@ import javax.security.auth.callback.CallbackHandler;
* <li>{@link #getName()} -- returns the common name of the SASL mechanism.</li> * <li>{@link #getName()} -- returns the common name of the SASL mechanism.</li>
* </ul> * </ul>
* 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, DomainBareJid, String)} -- Initiate authentication stanza using the * <li>{@link #authenticate(String, String, DomainBareJid, String, EntityBareJid)} -- Initiate authentication stanza using the
* deprecated method.</li> * deprecated method.</li>
* <li>{@link #authenticate(String, DomainBareJid, CallbackHandler)} -- Initiate authentication stanza * <li>{@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid)} -- Initiate authentication stanza
* using the CallbackHandler method.</li> * using the CallbackHandler method.</li>
* <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li> * <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li>
* </ul> * </ul>
@ -102,6 +103,12 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
*/ */
protected String authenticationId; protected String authenticationId;
/**
* The authorization identifier (authzid).
* This is always a bare Jid, but can be null.
*/
protected EntityBareJid authorizationId;
/** /**
* The name of the XMPP service * The name of the XMPP service
*/ */
@ -116,7 +123,7 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
/** /**
* 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 inflexible. Use * authentication is not recommended, since it is very inflexible. Use
* {@link #authenticate(String, DomainBareJid, CallbackHandler)} whenever possible. * {@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid)} whenever possible.
* *
* Explanation of auth stanza: * Explanation of auth stanza:
* *
@ -149,24 +156,27 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
* called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be * called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be
* "mail3.example.com". If the service is not replicated, or the serv-name is identical to * "mail3.example.com". If the service is not replicated, or the serv-name is identical to
* the host, then the serv-name component MUST be omitted * the host, then the serv-name component MUST be omitted
* *
* digest-uri verification is needed for ejabberd 2.0.3 and higher * digest-uri verification is needed for ejabberd 2.0.3 and higher
* *
* @param username the username of the user being authenticated. * @param username the username of the user being authenticated.
* @param host the hostname where the user account resides. * @param host the hostname where the user account resides.
* @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.
* @param authzid the optional authorization identity.
* @throws SmackException If a network error occurs while authenticating. * @throws SmackException If a network error occurs while authenticating.
* @throws NotConnectedException * @throws NotConnectedException
* @throws InterruptedException * @throws InterruptedException
*/ */
public final void authenticate(String username, String host, DomainBareJid serviceName, String password) public final void authenticate(String username, String host, DomainBareJid serviceName, String password, EntityBareJid authzid)
throws SmackException, NotConnectedException, InterruptedException { throws SmackException, NotConnectedException, InterruptedException {
this.authenticationId = username; this.authenticationId = username;
this.host = host; this.host = host;
this.serviceName = serviceName; this.serviceName = serviceName;
this.password = password; this.password = password;
this.authorizationId = authzid;
assert(authorizationId == null || authzidSupported());
authenticateInternal(); authenticateInternal();
authenticate(); authenticate();
} }
@ -184,14 +194,17 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
* @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 serviceName the xmpp service location
* @param cbh the CallbackHandler to obtain user information. * @param cbh the CallbackHandler to obtain user information.
* @param authzid the optional authorization identity.
* @throws SmackException * @throws SmackException
* @throws NotConnectedException * @throws NotConnectedException
* @throws InterruptedException * @throws InterruptedException
*/ */
public void authenticate(String host, DomainBareJid serviceName, CallbackHandler cbh) public void authenticate(String host, DomainBareJid serviceName, CallbackHandler cbh, EntityBareJid authzid)
throws SmackException, NotConnectedException, InterruptedException { throws SmackException, NotConnectedException, InterruptedException {
this.host = host; this.host = host;
this.serviceName = serviceName; this.serviceName = serviceName;
this.authorizationId = authzid;
assert(authorizationId == null || authzidSupported());
authenticateInternal(cbh); authenticateInternal(cbh);
authenticate(); authenticate();
} }
@ -283,6 +296,10 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
return saslMechansim; return saslMechansim;
} }
public boolean authzidSupported() {
return false;
}
protected abstract SASLMechanism newInstance(); protected abstract SASLMechanism newInstance();
protected static byte[] toBytes(String string) { protected static byte[] toBytes(String string) {

View File

@ -38,7 +38,6 @@ public class SCRAMSHA1Mechanism extends SASLMechanism {
public static final String NAME = "SCRAM-SHA-1"; public static final String NAME = "SCRAM-SHA-1";
private static final int RANDOM_ASCII_BYTE_COUNT = 32; private static final int RANDOM_ASCII_BYTE_COUNT = 32;
private static final String DEFAULT_GS2_HEADER = "n,,";
private static final byte[] CLIENT_KEY_BYTES = toBytes("Client Key"); private static final byte[] CLIENT_KEY_BYTES = toBytes("Client Key");
private static final byte[] SERVER_KEY_BYTES = toBytes("Server Key"); private static final byte[] SERVER_KEY_BYTES = toBytes("Server Key");
private static final byte[] ONE = new byte[] { 0, 0, 0, 1 }; private static final byte[] ONE = new byte[] { 0, 0, 0, 1 };
@ -77,7 +76,7 @@ public class SCRAMSHA1Mechanism extends SASLMechanism {
clientRandomAscii = getRandomAscii(); clientRandomAscii = getRandomAscii();
String saslPrepedAuthcId = saslPrep(authenticationId); String saslPrepedAuthcId = saslPrep(authenticationId);
clientFirstMessageBare = "n=" + escape(saslPrepedAuthcId) + ",r=" + clientRandomAscii; clientFirstMessageBare = "n=" + escape(saslPrepedAuthcId) + ",r=" + clientRandomAscii;
String clientFirstMessage = DEFAULT_GS2_HEADER + clientFirstMessageBare; String clientFirstMessage = getGS2Header() + clientFirstMessageBare;
state = State.AUTH_TEXT_SENT; state = State.AUTH_TEXT_SENT;
return toBytes(clientFirstMessage); return toBytes(clientFirstMessage);
} }
@ -105,6 +104,11 @@ public class SCRAMSHA1Mechanism extends SASLMechanism {
} }
} }
@Override
public boolean authzidSupported() {
return true;
}
@Override @Override
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException { protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
final String challengeString = new String(challenge); final String challengeString = new String(challenge);
@ -148,7 +152,7 @@ public class SCRAMSHA1Mechanism extends SASLMechanism {
// Parsing and error checking is done, we can now begin to calculate the values // Parsing and error checking is done, we can now begin to calculate the values
// First the client-final-message-without-proof // First the client-final-message-without-proof
String clientFinalMessageWithoutProof = "c=" + Base64.encode(DEFAULT_GS2_HEADER) + ",r=" + rvalue; String clientFinalMessageWithoutProof = "c=" + Base64.encode(getGS2Header()) + ",r=" + rvalue;
// AuthMessage := client-first-message-bare + "," + server-first-message + "," + // AuthMessage := client-first-message-bare + "," + server-first-message + "," +
// client-final-message-without-proof // client-final-message-without-proof
@ -209,6 +213,14 @@ public class SCRAMSHA1Mechanism extends SASLMechanism {
return null; return null;
} }
private final String getGS2Header() {
String authzidPortion = "";
if (authorizationId != null) {
authzidPortion = "a=" + authorizationId;
}
return "n," + authzidPortion + ",";
}
private static Map<Character, String> parseAttributes(String string) throws SmackException { private static Map<Character, String> parseAttributes(String string) throws SmackException {
if (string.length() == 0) { if (string.length() == 0) {
return Collections.emptyMap(); return Collections.emptyMap();

View File

@ -27,6 +27,7 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
import org.jxmpp.jid.EntityBareJid;
public class DigestMd5SaslTest extends AbstractSaslTest { public class DigestMd5SaslTest extends AbstractSaslTest {
@ -37,9 +38,12 @@ public class DigestMd5SaslTest extends AbstractSaslTest {
super(saslMechanism); super(saslMechanism);
} }
protected void runTest() throws NotConnectedException, SmackException, InterruptedException, XmppStringprepException { protected void runTest(boolean useAuthzid) throws NotConnectedException, SmackException, InterruptedException, XmppStringprepException {
saslMechanism.authenticate("florian", "irrelevant", JidCreate.domainBareFrom("xmpp.org"), "secret"); EntityBareJid authzid = null;
if (useAuthzid) {
authzid = JidCreate.entityBareFrom("shazbat@xmpp.org");
}
saslMechanism.authenticate("florian", "irrelevant", JidCreate.domainBareFrom("xmpp.org"), "secret", authzid);
byte[] response = saslMechanism.evaluateChallenge(challengeBytes); byte[] response = saslMechanism.evaluateChallenge(challengeBytes);
String responseString = new String(response); String responseString = new String(response);
String[] responseParts = responseString.split(","); String[] responseParts = responseString.split(",");
@ -51,6 +55,11 @@ public class DigestMd5SaslTest extends AbstractSaslTest {
String value = keyValue[1].replace("\"", ""); String value = keyValue[1].replace("\"", "");
responsePairs.put(key, value); responsePairs.put(key, value);
} }
if (useAuthzid) {
assertMapValue("authzid", "shazbat@xmpp.org", responsePairs);
} else {
assert(!responsePairs.containsKey("authzid"));
}
assertMapValue("username", "florian", responsePairs); assertMapValue("username", "florian", responsePairs);
assertMapValue("realm", "xmpp.org", responsePairs); assertMapValue("realm", "xmpp.org", responsePairs);
assertMapValue("digest-uri", "xmpp/xmpp.org", responsePairs); assertMapValue("digest-uri", "xmpp/xmpp.org", responsePairs);
@ -58,6 +67,6 @@ public class DigestMd5SaslTest extends AbstractSaslTest {
} }
private static void assertMapValue(String key, String value, Map<String, String> map) { private static void assertMapValue(String key, String value, Map<String, String> map) {
assertEquals(map.get(key), value); assertEquals(value, map.get(key));
} }
} }

View File

@ -48,7 +48,7 @@ public class SCRAMSHA1MechanismTest extends SmackTestSuite {
} }
}; };
mech.authenticate(USERNAME, "unusedFoo", JidTestUtil.DOMAIN_BARE_JID_1, PASSWORD); mech.authenticate(USERNAME, "unusedFoo", JidTestUtil.DOMAIN_BARE_JID_1, PASSWORD, null);
AuthMechanism authMechanism = con.getSentPacket(); AuthMechanism authMechanism = con.getSentPacket();
assertEquals(SCRAMSHA1Mechanism.NAME, authMechanism.getMechanism()); assertEquals(SCRAMSHA1Mechanism.NAME, authMechanism.getMechanism());
assertEquals(CLIENT_FIRST_MESSAGE, saslLayerString(authMechanism.getAuthenticationText())); assertEquals(CLIENT_FIRST_MESSAGE, saslLayerString(authMechanism.getAuthenticationText()));

View File

@ -25,6 +25,11 @@ public class SASLDigestMD5Mechanism extends SASLJavaXMechanism {
public static final String NAME = DIGESTMD5; public static final String NAME = DIGESTMD5;
@Override
public boolean authzidSupported() {
return true;
}
public String getName() { public String getName() {
return NAME; return NAME;
} }

View File

@ -46,6 +46,11 @@ public class SASLExternalMechanism extends SASLJavaXMechanism {
public static final String NAME = EXTERNAL; public static final String NAME = EXTERNAL;
@Override
public boolean authzidSupported() {
return true;
}
@Override @Override
public String getName() { public String getName() {
return EXTERNAL; return EXTERNAL;

View File

@ -34,6 +34,11 @@ public class SASLGSSAPIMechanism extends SASLJavaXMechanism {
System.setProperty("java.security.auth.login.config","gss.conf"); System.setProperty("java.security.auth.login.config","gss.conf");
} }
@Override
public boolean authzidSupported() {
return true;
}
@Override @Override
public String getName() { public String getName() {
return NAME; return NAME;

View File

@ -53,8 +53,12 @@ public abstract class SASLJavaXMechanism extends SASLMechanism {
throws SmackException { throws SmackException {
String[] mechanisms = { getName() }; String[] mechanisms = { getName() };
Map<String, String> props = getSaslProps(); Map<String, String> props = getSaslProps();
String authzid = null;
if (authorizationId != null) {
authzid = authorizationId.toString();
}
try { try {
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", getServerName().toString(), props, sc = Sasl.createSaslClient(mechanisms, authzid, "xmpp", getServerName().toString(), props,
new CallbackHandler() { new CallbackHandler() {
@Override @Override
public void handle(Callback[] callbacks) throws IOException, public void handle(Callback[] callbacks) throws IOException,

View File

@ -29,6 +29,11 @@ public class SASLPlainMechanism extends SASLJavaXMechanism {
return NAME; return NAME;
} }
@Override
public boolean authzidSupported() {
return true;
}
@Override @Override
public int getPriority() { public int getPriority() {
return 400; return 400;

View File

@ -30,6 +30,11 @@ public class SASLDigestMD5Test extends DigestMd5SaslTest {
@Test @Test
public void testDigestMD5() throws NotConnectedException, SmackException, InterruptedException, XmppStringprepException { public void testDigestMD5() throws NotConnectedException, SmackException, InterruptedException, XmppStringprepException {
runTest(); runTest(false);
}
@Test
public void testDigestMD5Authzid() throws NotConnectedException, SmackException, InterruptedException, XmppStringprepException {
runTest(true);
} }
} }

View File

@ -83,6 +83,11 @@ public class SASLDigestMD5Mechanism extends SASLMechanism {
return new SASLDigestMD5Mechanism(); return new SASLDigestMD5Mechanism();
} }
@Override
public boolean authzidSupported() {
return true;
}
@Override @Override
public void checkIfSuccessfulOrThrow() throws SmackException { public void checkIfSuccessfulOrThrow() throws SmackException {
@ -141,7 +146,14 @@ public class SASLDigestMD5Mechanism extends SASLMechanism {
String responseValue = calcResponse(DigestType.ClientResponse); String responseValue = calcResponse(DigestType.ClientResponse);
// @formatter:off // @formatter:off
// See RFC 2831 2.1.2 digest-response // See RFC 2831 2.1.2 digest-response
String authzid;
if (authorizationId == null) {
authzid = "";
} else {
authzid = ",authzid=\"" + authorizationId + '"';
}
String saslString = "username=\"" + authenticationId + '"' String saslString = "username=\"" + authenticationId + '"'
+ authzid
+ ",realm=\"" + serviceName + '"' + ",realm=\"" + serviceName + '"'
+ ",nonce=\"" + nonce + '"' + ",nonce=\"" + nonce + '"'
+ ",cnonce=\"" + cnonce + '"' + ",cnonce=\"" + cnonce + '"'

View File

@ -40,6 +40,10 @@ public class SASLExternalMechanism extends SASLMechanism {
@Override @Override
protected byte[] getAuthenticationText() throws SmackException { protected byte[] getAuthenticationText() throws SmackException {
if (authorizationId != null) {
return toBytes(authorizationId.toString());
}
if (StringUtils.isNullOrEmpty(authenticationId)) { if (StringUtils.isNullOrEmpty(authenticationId)) {
return null; return null;
} }
@ -67,4 +71,9 @@ public class SASLExternalMechanism extends SASLMechanism {
// No check performed // No check performed
} }
@Override
public boolean authzidSupported() {
return true;
}
} }

View File

@ -34,7 +34,13 @@ public class SASLPlainMechanism extends SASLMechanism {
@Override @Override
protected byte[] getAuthenticationText() throws SmackException { protected byte[] getAuthenticationText() throws SmackException {
// concatenate and encode username (authcid) and password // concatenate and encode username (authcid) and password
byte[] authcid = toBytes('\u0000' + authenticationId); String authzid;
if (authorizationId == null) {
authzid = "";
} else {
authzid = authorizationId.toString();
}
byte[] authcid = toBytes(authzid + '\u0000' + authenticationId);
byte[] passw = toBytes('\u0000' + password); byte[] passw = toBytes('\u0000' + password);
return ByteUtils.concact(authcid, passw); return ByteUtils.concact(authcid, passw);
@ -59,4 +65,9 @@ public class SASLPlainMechanism extends SASLMechanism {
public void checkIfSuccessfulOrThrow() throws SmackException { public void checkIfSuccessfulOrThrow() throws SmackException {
// No check performed // No check performed
} }
@Override
public boolean authzidSupported() {
return true;
}
} }

View File

@ -29,6 +29,11 @@ public class SASLDigestMD5Test extends DigestMd5SaslTest {
@Test @Test
public void testDigestMD5() throws NotConnectedException, SmackException, InterruptedException, XmppStringprepException { public void testDigestMD5() throws NotConnectedException, SmackException, InterruptedException, XmppStringprepException {
runTest(); runTest(false);
}
@Test
public void testDigestMD5Authzid() throws NotConnectedException, SmackException, InterruptedException, XmppStringprepException {
runTest(true);
} }
} }

View File

@ -376,7 +376,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
protected synchronized void loginInternal(String username, String password, Resourcepart resource) throws XMPPException, protected synchronized void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
SmackException, IOException, InterruptedException { SmackException, IOException, InterruptedException {
// Authenticate using SASL // Authenticate using SASL
saslAuthentication.authenticate(username, password); saslAuthentication.authenticate(username, password, config.getAuthzid());
// 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
// recommends to perform stream compression before resource binding. // recommends to perform stream compression before resource binding.