Detailed changes
@@ -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 "";
+ }
}
@@ -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;
+ }
}
@@ -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);
+ }
}
@@ -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);
+ }
}
@@ -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 "";
+ }
}
@@ -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,,";
@@ -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;
+ }
}
@@ -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;
+ }
}
@@ -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;
+ }
+}
@@ -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();
+ }
}
@@ -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);
+ }
+ }
});
}