implement SCRAM-SHA512

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java      |  38 
src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java      | 143 
src/main/java/eu/siacs/conversations/crypto/sasl/External.java       |  33 
src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java          |  49 
src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java  |  97 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java |   3 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java      |  48 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java    |  48 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java    |  39 
src/main/java/eu/siacs/conversations/crypto/sasl/Tokenizer.java      | 120 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java        |  60 
11 files changed, 367 insertions(+), 311 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java 🔗

@@ -7,22 +7,24 @@ import eu.siacs.conversations.xml.TagWriter;
 
 public class Anonymous extends SaslMechanism {
 
-	public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
-		super(tagWriter, account, rng);
-	}
-
-	@Override
-	public int getPriority() {
-		return 0;
-	}
-
-	@Override
-	public String getMechanism() {
-		return "ANONYMOUS";
-	}
-
-	@Override
-	public String getClientFirstMessage() {
-		return "";
-	}
+    public static final String MECHANISM = "ANONYMOUS";
+
+    public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
+        super(tagWriter, account, rng);
+    }
+
+    @Override
+    public int getPriority() {
+        return 0;
+    }
+
+    @Override
+    public String getMechanism() {
+        return MECHANISM;
+    }
+
+    @Override
+    public String getClientFirstMessage() {
+        return "";
+    }
 }

src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java 🔗

@@ -12,79 +12,82 @@ import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.xml.TagWriter;
 
 public class DigestMd5 extends SaslMechanism {
-	public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
-		super(tagWriter, account, rng);
-	}
 
-	@Override
-	public int getPriority() {
-		return 10;
-	}
+    public static final String MECHANISM = "DIGEST-MD5";
 
-	@Override
-	public String getMechanism() {
-		return "DIGEST-MD5";
-	}
+    public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+        super(tagWriter, account, rng);
+    }
 
-	private State state = State.INITIAL;
+    @Override
+    public int getPriority() {
+        return 10;
+    }
 
-	@Override
-	public String getResponse(final String challenge) throws AuthenticationException {
-		switch (state) {
-			case INITIAL:
-				state = State.RESPONSE_SENT;
-				final String encodedResponse;
-				try {
-					final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
-					String nonce = "";
-					for (final String token : tokenizer) {
-						final String[] parts = token.split("=", 2);
-						if (parts[0].equals("nonce")) {
-							nonce = parts[1].replace("\"", "");
-						} else if (parts[0].equals("rspauth")) {
-							return "";
-						}
-					}
-					final String digestUri = "xmpp/" + account.getServer();
-					final String nonceCount = "00000001";
-					final String x = account.getUsername() + ":" + account.getServer() + ":"
-						+ account.getPassword();
-					final MessageDigest md = MessageDigest.getInstance("MD5");
-					final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
-					final String cNonce = CryptoHelper.random(100,rng);
-					final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
-							(":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
-					final String a2 = "AUTHENTICATE:" + digestUri;
-					final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
-					final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
-									.defaultCharset())));
-					final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
-						+ ":auth:" + ha2;
-					final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
-									.defaultCharset())));
-					final String saslString = "username=\"" + account.getUsername()
-						+ "\",realm=\"" + account.getServer() + "\",nonce=\""
-						+ nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
-						+ ",qop=auth,digest-uri=\"" + digestUri + "\",response="
-						+ response + ",charset=utf-8";
-					encodedResponse = Base64.encodeToString(
-							saslString.getBytes(Charset.defaultCharset()),
-							Base64.NO_WRAP);
-				} catch (final NoSuchAlgorithmException e) {
-					throw new AuthenticationException(e);
-				}
+    @Override
+    public String getMechanism() {
+        return MECHANISM;
+    }
 
-				return encodedResponse;
-			case RESPONSE_SENT:
-				state = State.VALID_SERVER_RESPONSE;
-				break;
-			case VALID_SERVER_RESPONSE:
-				if (challenge==null) {
-					return null; //everything is fine
-				}
-			default:
-				throw new InvalidStateException(state);
-		}
-		return null;
-	}
+    private State state = State.INITIAL;
+
+    @Override
+    public String getResponse(final String challenge) throws AuthenticationException {
+        switch (state) {
+            case INITIAL:
+                state = State.RESPONSE_SENT;
+                final String encodedResponse;
+                try {
+                    final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
+                    String nonce = "";
+                    for (final String token : tokenizer) {
+                        final String[] parts = token.split("=", 2);
+                        if (parts[0].equals("nonce")) {
+                            nonce = parts[1].replace("\"", "");
+                        } else if (parts[0].equals("rspauth")) {
+                            return "";
+                        }
+                    }
+                    final String digestUri = "xmpp/" + account.getServer();
+                    final String nonceCount = "00000001";
+                    final String x = account.getUsername() + ":" + account.getServer() + ":"
+                            + account.getPassword();
+                    final MessageDigest md = MessageDigest.getInstance("MD5");
+                    final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
+                    final String cNonce = CryptoHelper.random(100, rng);
+                    final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
+                            (":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
+                    final String a2 = "AUTHENTICATE:" + digestUri;
+                    final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
+                    final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
+                            .defaultCharset())));
+                    final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
+                            + ":auth:" + ha2;
+                    final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
+                            .defaultCharset())));
+                    final String saslString = "username=\"" + account.getUsername()
+                            + "\",realm=\"" + account.getServer() + "\",nonce=\""
+                            + nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
+                            + ",qop=auth,digest-uri=\"" + digestUri + "\",response="
+                            + response + ",charset=utf-8";
+                    encodedResponse = Base64.encodeToString(
+                            saslString.getBytes(Charset.defaultCharset()),
+                            Base64.NO_WRAP);
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new AuthenticationException(e);
+                }
+
+                return encodedResponse;
+            case RESPONSE_SENT:
+                state = State.VALID_SERVER_RESPONSE;
+                break;
+            case VALID_SERVER_RESPONSE:
+                if (challenge == null) {
+                    return null; //everything is fine
+                }
+            default:
+                throw new InvalidStateException(state);
+        }
+        return null;
+    }
 }

src/main/java/eu/siacs/conversations/crypto/sasl/External.java 🔗

@@ -1,6 +1,7 @@
 package eu.siacs.conversations.crypto.sasl;
 
 import android.util.Base64;
+
 import java.security.SecureRandom;
 
 import eu.siacs.conversations.entities.Account;
@@ -8,22 +9,24 @@ import eu.siacs.conversations.xml.TagWriter;
 
 public class External extends SaslMechanism {
 
-	public External(TagWriter tagWriter, Account account, SecureRandom rng) {
-		super(tagWriter, account, rng);
-	}
+    public static final String MECHANISM = "EXTERNAL";
+
+    public External(TagWriter tagWriter, Account account, SecureRandom rng) {
+        super(tagWriter, account, rng);
+    }
 
-	@Override
-	public int getPriority() {
-		return 25;
-	}
+    @Override
+    public int getPriority() {
+        return 25;
+    }
 
-	@Override
-	public String getMechanism() {
-		return "EXTERNAL";
-	}
+    @Override
+    public String getMechanism() {
+        return MECHANISM;
+    }
 
-	@Override
-	public String getClientFirstMessage() {
-		return Base64.encodeToString(account.getJid().asBareJid().toEscapedString().getBytes(),Base64.NO_WRAP);
-	}
+    @Override
+    public String getClientFirstMessage() {
+        return Base64.encodeToString(account.getJid().asBareJid().toEscapedString().getBytes(), Base64.NO_WRAP);
+    }
 }

src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java 🔗

@@ -8,27 +8,30 @@ import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.xml.TagWriter;
 
 public class Plain extends SaslMechanism {
-	public Plain(final TagWriter tagWriter, final Account account) {
-		super(tagWriter, account, null);
-	}
-
-	@Override
-	public int getPriority() {
-		return 10;
-	}
-
-	@Override
-	public String getMechanism() {
-		return "PLAIN";
-	}
-
-	@Override
-	public String getClientFirstMessage() {
-		return getMessage(account.getUsername(), account.getPassword());
-	}
-
-	public static String getMessage(String username, String password) {
-		final String message = '\u0000' + username + '\u0000' + password;
-		return Base64.encodeToString(message.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
-	}
+
+    public static final String MECHANISM = "PLAIN";
+
+    public Plain(final TagWriter tagWriter, final Account account) {
+        super(tagWriter, account, null);
+    }
+
+    @Override
+    public int getPriority() {
+        return 10;
+    }
+
+    @Override
+    public String getMechanism() {
+        return MECHANISM;
+    }
+
+    @Override
+    public String getClientFirstMessage() {
+        return getMessage(account.getUsername(), account.getPassword());
+    }
+
+    public static String getMessage(String username, String password) {
+        final String message = '\u0000' + username + '\u0000' + password;
+        return Base64.encodeToString(message.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
+    }
 }

src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java 🔗

@@ -7,60 +7,63 @@ import eu.siacs.conversations.xml.TagWriter;
 
 public abstract class SaslMechanism {
 
-	final protected TagWriter tagWriter;
-	final protected Account account;
-	final protected SecureRandom rng;
+    final protected TagWriter tagWriter;
+    final protected Account account;
+    final protected SecureRandom rng;
 
-	protected enum State {
-		INITIAL,
-		AUTH_TEXT_SENT,
-		RESPONSE_SENT,
-		VALID_SERVER_RESPONSE,
-	}
+    protected enum State {
+        INITIAL,
+        AUTH_TEXT_SENT,
+        RESPONSE_SENT,
+        VALID_SERVER_RESPONSE,
+    }
 
-	public static class AuthenticationException extends Exception {
-		public AuthenticationException(final String message) {
-			super(message);
-		}
+    public static class AuthenticationException extends Exception {
+        public AuthenticationException(final String message) {
+            super(message);
+        }
 
-		public AuthenticationException(final Exception inner) {
-			super(inner);
-		}
+        public AuthenticationException(final Exception inner) {
+            super(inner);
+        }
 
-		public AuthenticationException(final String message, final Exception exception) {
-			super(message,exception);
-		}
-	}
+        public AuthenticationException(final String message, final Exception exception) {
+            super(message, exception);
+        }
+    }
 
-	public static class InvalidStateException extends AuthenticationException {
-		public InvalidStateException(final String message) {
-			super(message);
-		}
+    public static class InvalidStateException extends AuthenticationException {
+        public InvalidStateException(final String message) {
+            super(message);
+        }
 
-		public InvalidStateException(final State state) {
-			this("Invalid state: " + state.toString());
-		}
-	}
+        public InvalidStateException(final State state) {
+            this("Invalid state: " + state.toString());
+        }
+    }
 
-	public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
-		this.tagWriter = tagWriter;
-		this.account = account;
-		this.rng = rng;
-	}
+    public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+        this.tagWriter = tagWriter;
+        this.account = account;
+        this.rng = rng;
+    }
 
-	/**
-	 * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
-	 * mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
-	 * attacks).
-	 * @return An arbitrary int representing the priority
-	 */
-	public abstract int getPriority();
+    /**
+     * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
+     * mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
+     * attacks).
+     *
+     * @return An arbitrary int representing the priority
+     */
+    public abstract int getPriority();
 
-	public abstract String getMechanism();
-	public String getClientFirstMessage() {
-		return "";
-	}
-	public String getResponse(final String challenge) throws AuthenticationException {
-		return "";
-	}
+    public abstract String getMechanism();
+
+    public String getClientFirstMessage() {
+        return "";
+    }
+
+    public String getResponse(final String challenge) throws AuthenticationException {
+        return "";
+    }
 }

src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java 🔗

@@ -1,7 +1,5 @@
 package eu.siacs.conversations.crypto.sasl;
 
-import android.annotation.TargetApi;
-import android.os.Build;
 import android.util.Base64;
 
 import com.google.common.base.Objects;
@@ -21,7 +19,6 @@ import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.xml.TagWriter;
 
-@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 abstract class ScramMechanism extends SaslMechanism {
     // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
     private final static String GS2_HEADER = "n,,";

src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java 🔗

@@ -11,27 +11,29 @@ import eu.siacs.conversations.xml.TagWriter;
 
 public class ScramSha1 extends ScramMechanism {
 
-	@Override
-	protected HMac getHMAC() {
-		return  new HMac(new SHA1Digest());
-	}
-
-	@Override
-	protected Digest getDigest() {
-		return new SHA1Digest();
-	}
-
-	public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
-		super(tagWriter, account, rng);
-	}
-
-	@Override
-	public int getPriority() {
-		return 20;
-	}
-
-	@Override
-	public String getMechanism() {
-		return "SCRAM-SHA-1";
-	}
+    public static final String MECHANISM = "SCRAM-SHA-1";
+
+    @Override
+    protected HMac getHMAC() {
+        return new HMac(new SHA1Digest());
+    }
+
+    @Override
+    protected Digest getDigest() {
+        return new SHA1Digest();
+    }
+
+    public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+        super(tagWriter, account, rng);
+    }
+
+    @Override
+    public int getPriority() {
+        return 20;
+    }
+
+    @Override
+    public String getMechanism() {
+        return MECHANISM;
+    }
 }

src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java 🔗

@@ -11,27 +11,29 @@ import eu.siacs.conversations.xml.TagWriter;
 
 public class ScramSha256 extends ScramMechanism {
 
-	@Override
-	protected HMac getHMAC() {
-		return new HMac(new SHA256Digest());
-	}
-
-	@Override
-	protected Digest getDigest() {
-		return new SHA256Digest();
-	}
-
-	public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
-		super(tagWriter, account, rng);
-	}
-
-	@Override
-	public int getPriority() {
-		return 25;
-	}
-
-	@Override
-	public String getMechanism() {
-		return "SCRAM-SHA-256";
-	}
+    public static final String MECHANISM = "SCRAM-SHA-256";
+
+    @Override
+    protected HMac getHMAC() {
+        return new HMac(new SHA256Digest());
+    }
+
+    @Override
+    protected Digest getDigest() {
+        return new SHA256Digest();
+    }
+
+    public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+        super(tagWriter, account, rng);
+    }
+
+    @Override
+    public int getPriority() {
+        return 25;
+    }
+
+    @Override
+    public String getMechanism() {
+        return MECHANISM;
+    }
 }

src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java 🔗

@@ -0,0 +1,39 @@
+package eu.siacs.conversations.crypto.sasl;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.macs.HMac;
+
+import java.security.SecureRandom;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.xml.TagWriter;
+
+public class ScramSha512 extends ScramMechanism {
+
+    public static final String MECHANISM = "SCRAM-SHA-512";
+
+    @Override
+    protected HMac getHMAC() {
+        return new HMac(new SHA512Digest());
+    }
+
+    @Override
+    protected Digest getDigest() {
+        return new SHA512Digest();
+    }
+
+    public ScramSha512(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+        super(tagWriter, account, rng);
+    }
+
+    @Override
+    public int getPriority() {
+        return 30;
+    }
+
+    @Override
+    public String getMechanism() {
+        return MECHANISM;
+    }
+}

src/main/java/eu/siacs/conversations/crypto/sasl/Tokenizer.java 🔗

@@ -10,69 +10,69 @@ import java.util.NoSuchElementException;
  * A tokenizer for GS2 header strings
  */
 public final class Tokenizer implements Iterator<String>, Iterable<String> {
-	private final List<String> parts;
-	private int index;
+    private final List<String> parts;
+    private int index;
 
-	public Tokenizer(final byte[] challenge) {
-		final String challengeString = new String(challenge);
-		parts = new ArrayList<>(Arrays.asList(challengeString.split(",")));
-		// Trim parts.
-		for (int i = 0; i < parts.size(); i++) {
-			parts.set(i, parts.get(i).trim());
-		}
-		index = 0;
-	}
+    public Tokenizer(final byte[] challenge) {
+        final String challengeString = new String(challenge);
+        parts = new ArrayList<>(Arrays.asList(challengeString.split(",")));
+        // Trim parts.
+        for (int i = 0; i < parts.size(); i++) {
+            parts.set(i, parts.get(i).trim());
+        }
+        index = 0;
+    }
 
-	/**
-	 * Returns true if there is at least one more element, false otherwise.
-	 *
-	 * @see #next
-	 */
-	@Override
-	public boolean hasNext() {
-		return parts.size() != index + 1;
-	}
+    /**
+     * Returns true if there is at least one more element, false otherwise.
+     *
+     * @see #next
+     */
+    @Override
+    public boolean hasNext() {
+        return parts.size() != index + 1;
+    }
 
-	/**
-	 * Returns the next object and advances the iterator.
-	 *
-	 * @return the next object.
-	 * @throws java.util.NoSuchElementException if there are no more elements.
-	 * @see #hasNext
-	 */
-	@Override
-	public String next() {
-		if (hasNext()) {
-			return parts.get(index++);
-		} else {
-			throw new NoSuchElementException("No such element. Size is: " + parts.size());
-		}
-	}
+    /**
+     * Returns the next object and advances the iterator.
+     *
+     * @return the next object.
+     * @throws java.util.NoSuchElementException if there are no more elements.
+     * @see #hasNext
+     */
+    @Override
+    public String next() {
+        if (hasNext()) {
+            return parts.get(index++);
+        } else {
+            throw new NoSuchElementException("No such element. Size is: " + parts.size());
+        }
+    }
 
-	/**
-	 * Removes the last object returned by {@code next} from the collection.
-	 * This method can only be called once between each call to {@code next}.
-	 *
-	 * @throws UnsupportedOperationException if removing is not supported by the collection being
-	 *                                       iterated.
-	 * @throws IllegalStateException         if {@code next} has not been called, or {@code remove} has
-	 *                                       already been called after the last call to {@code next}.
-	 */
-	@Override
-	public void remove() {
-		if(index <= 0) {
-			throw new IllegalStateException("You can't delete an element before first next() method call");
-		}
-		parts.remove(--index);
-	}
+    /**
+     * Removes the last object returned by {@code next} from the collection.
+     * This method can only be called once between each call to {@code next}.
+     *
+     * @throws UnsupportedOperationException if removing is not supported by the collection being
+     *                                       iterated.
+     * @throws IllegalStateException         if {@code next} has not been called, or {@code remove} has
+     *                                       already been called after the last call to {@code next}.
+     */
+    @Override
+    public void remove() {
+        if (index <= 0) {
+            throw new IllegalStateException("You can't delete an element before first next() method call");
+        }
+        parts.remove(--index);
+    }
 
-	/**
-	 * Returns an {@link java.util.Iterator} for the elements in this object.
-	 *
-	 * @return An {@code Iterator} instance.
-	 */
-	@Override
-	public Iterator<String> iterator() {
-		return parts.iterator();
-	}
+    /**
+     * Returns an {@link java.util.Iterator} for the elements in this object.
+     *
+     * @return An {@code Iterator} instance.
+     */
+    @Override
+    public Iterator<String> iterator() {
+        return parts.iterator();
+    }
 }

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -64,6 +64,7 @@ import eu.siacs.conversations.crypto.sasl.Plain;
 import eu.siacs.conversations.crypto.sasl.SaslMechanism;
 import eu.siacs.conversations.crypto.sasl.ScramSha1;
 import eu.siacs.conversations.crypto.sasl.ScramSha256;
+import eu.siacs.conversations.crypto.sasl.ScramSha512;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.ServiceDiscoveryResult;
@@ -870,20 +871,21 @@ public class XmppConnection implements Runnable {
     }
 
     private void authenticate() throws IOException {
-        final List<String> mechanisms = extractMechanisms(streamFeatures
-                .findChild("mechanisms"));
+        final List<String> mechanisms = extractMechanisms(streamFeatures.findChild("mechanisms"));
         final Element auth = new Element("auth", Namespace.SASL);
-        if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) {
+        if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) {
             saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG());
-        } else if (mechanisms.contains("SCRAM-SHA-256")) {
+        } else if (mechanisms.contains(ScramSha512.MECHANISM)) {
+            saslMechanism = new ScramSha512(tagWriter, account, mXmppConnectionService.getRNG());
+        } else if (mechanisms.contains(ScramSha256.MECHANISM)) {
             saslMechanism = new ScramSha256(tagWriter, account, mXmppConnectionService.getRNG());
-        } else if (mechanisms.contains("SCRAM-SHA-1")) {
+        } else if (mechanisms.contains(ScramSha1.MECHANISM)) {
             saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG());
-        } else if (mechanisms.contains("PLAIN") && !account.getJid().getDomain().toEscapedString().equals("nimbuzz.com")) {
+        } else if (mechanisms.contains(Plain.MECHANISM) && !account.getJid().getDomain().toEscapedString().equals("nimbuzz.com")) {
             saslMechanism = new Plain(tagWriter, account);
-        } else if (mechanisms.contains("DIGEST-MD5")) {
+        } else if (mechanisms.contains(DigestMd5.MECHANISM)) {
             saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG());
-        } else if (mechanisms.contains("ANONYMOUS")) {
+        } else if (mechanisms.contains(Anonymous.MECHANISM)) {
             saslMechanism = new Anonymous(tagWriter, account, mXmppConnectionService.getRNG());
         }
         if (saslMechanism != null) {
@@ -1265,27 +1267,27 @@ public class XmppConnection implements Runnable {
         request.setTo(account.getDomain());
         request.addChild("query", Namespace.DISCO_ITEMS).setAttribute("node", Namespace.COMMANDS);
         sendIqPacket(request, (account, response) -> {
-           if (response.getType() == IqPacket.TYPE.RESULT) {
-               final Element query = response.findChild("query",Namespace.DISCO_ITEMS);
-               if (query == null) {
-                   return;
-               }
-               final HashMap<String, Jid> commands = new HashMap<>();
-               for(final Element child : query.getChildren()) {
-                   if ("item".equals(child.getName())) {
-                       final String node = child.getAttribute("node");
-                       final Jid jid = child.getAttributeAsJid("jid");
-                       if (node != null && jid != null) {
-                           commands.put(node, jid);
-                       }
-                   }
-               }
-               Log.d(Config.LOGTAG,commands.toString());
-               synchronized (this.commands) {
-                   this.commands.clear();
-                   this.commands.putAll(commands);
-               }
-           }
+            if (response.getType() == IqPacket.TYPE.RESULT) {
+                final Element query = response.findChild("query", Namespace.DISCO_ITEMS);
+                if (query == null) {
+                    return;
+                }
+                final HashMap<String, Jid> commands = new HashMap<>();
+                for (final Element child : query.getChildren()) {
+                    if ("item".equals(child.getName())) {
+                        final String node = child.getAttribute("node");
+                        final Jid jid = child.getAttributeAsJid("jid");
+                        if (node != null && jid != null) {
+                            commands.put(node, jid);
+                        }
+                    }
+                }
+                Log.d(Config.LOGTAG, commands.toString());
+                synchronized (this.commands) {
+                    this.commands.clear();
+                    this.commands.putAll(commands);
+                }
+            }
         });
     }