EXTERNAL authentication should accept empty or null username/password

I tried to authenticate with EXTERNAL mech but I got an IllegalArgumentException due to empty username.

Look the code in AbstractXMPPConnection.java:

** public void login(String username, String password, String resource) throws XMPPException,**

** SmackException, IOException {**

** if (StringUtils.isNullOrEmpty(username)) {**

** throw new IllegalArgumentException(“Username must not be null or empty”);**

** }**

** usedUsername = username;**

** usedPassword = password;**

** usedResource = resource;**

** loginNonAnonymously(username, password, resource);**

** }**

I think it should allow null usernames. The XEP-0178 says:

If the client certificate contains only one JID, then the client MAY include an authorization identity, but only if it desires to be authorized as a JID other than the address in the client certificate; else it MUST NOT include an authorization identity (this is shown in the following example by setting the XML character data of the element to “=”).

Example 9.

<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'
      mechanism='EXTERNAL'>=</auth>

SMACK-627

I propose Add allowEmptyOrNullUsername() · 7e4e369 · Flowdalic/Smack · GitHub to address SMACK-627. Please test and report back if it works for you.

1 Like

I tested the changes today.

Now, the login is thinking that the authentication is anonymous.

public final boolean isAnonymous() {

    return config.getUsername() == null && usedUsername == null;

}

I changed to

@Override

public final boolean isAnonymous() {

    return **!config.allowNullOrEmptyUsername** && config.getUsername() == null && usedUsername == null;

}

but the password is null, and the callback handler of the Sasl is being invoked, throwing java.lang.UnsupportedOperationException: CallbackHandler not (yet) supported:

protected synchronized void loginNonAnonymously(String username, String password, String resource) throws XMPPException, SmackException, IOException {

    if (saslAuthentication.hasNonAnonymousAuthentication()) {

        // Authenticate using SASL

        if (password != null) {

            saslAuthentication.authenticate(username, password, resource);

        }

        else {

            saslAuthentication.authenticate(resource, config.getCallbackHandler());

        }

So I changed to

protected synchronized void loginNonAnonymously(String username, String password, String resource) throws XMPPException, SmackException, IOException {

    if (saslAuthentication.hasNonAnonymousAuthentication()) {

        // Authenticate using SASL

        if (password != null **|| config.isNullOrEmptyUsernameAllowed()**) {** // I had to expose the allowNullOrEmptyUsername flag**

            saslAuthentication.authenticate(username, password, resource);

        }

        else {

            saslAuthentication.authenticate(resource, config.getCallbackHandler());

        }

Now it worked!!

Thanks for the feedback. Would you try if Fix SASL EXTERNAL with client cert provided JID · 1bdd839 · Flowdalic/Smack · GitHub works too? Compared to your changes this doesn’t need any changes in loginNonAnonymously and doesn’t need to expose the flag.

It has not worked. The is authenticationId is null.

I think the authenticateInternal(CallbackHandler cbh) should do nothing in the SASLExternalMechanism class.

Isn’t this the case with Fix SASL EXTERNAL with client cert provided JID · 1bdd839 · Flowdalic/Smack · GitHub ?

If authenticationId is null, StringUtils.isNotEmpty(authenticationId) is false, so the exception will be thrown.

Anyway, whenever the user use External mech, the password should be null, and authenticateInternal(CallbackHandler cbh) will be invoked. If authenticationId is null or specific, should be a user decision. For this reason that I think that the authenticateInternal(CallbackHandler cbh) method should do nothing.

Ahh, stupid me. I meant to write and read all the time isNullOrEmpty. Fixed with Fix SASL EXTERNAL with client cert provided JID · c1ad39b · Flowdalic/Smack · GitHub

Now it worked! Thanks!

I’m thinking about this case mentioned in the XEP-0178:

If the client certificate contains more than one JID, then the client MUST include an authorization identity so that the server can determine which JID to use (this is shown in the following example by setting the XML character data of the element to “anVsaWV0QGV4YW1wbGUuY29t”, which is the base 64 encoding for "juliet@example.com").

Example 10.

<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'
      mechanism='EXTERNAL'>anVsaWV0QGV4YW1wbGUuY29t</auth>

In order to this case works, the configuration builder should use empty string password because null password calls authenticateInternal(CallbackHandler cbh) and throws UnsupportedOperationException("CallbackHandler not (yet) supported"):

XMPPTCPConnectionConfiguration.builder() //

.setUsernameAndPassword(“juliet”, “”) // works!

.setUsernameAndPassword(“juliet”, null) // not work!

Yep, I also came to the conclusion that the method for SASL EXTERNAL should be simply empty. See Fix SASL EXTERNAL with client cert provided JID · c682091 · Flowdalic/Smack · GitHub