mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-21 22:02:06 +01:00
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:
parent
a00331dbb4
commit
9c772add93
18 changed files with 182 additions and 32 deletions
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 + '"'
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue