implement SCRAM-SHA512

This commit is contained in:
Daniel Gultsch 2020-12-31 09:32:05 +01:00
parent 2a57c92f63
commit 0e54d8a2cf
11 changed files with 352 additions and 296 deletions

View file

@ -7,6 +7,8 @@ import eu.siacs.conversations.xml.TagWriter;
public class Anonymous extends SaslMechanism {
public static final String MECHANISM = "ANONYMOUS";
public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
super(tagWriter, account, rng);
}
@ -18,7 +20,7 @@ public class Anonymous extends SaslMechanism {
@Override
public String getMechanism() {
return "ANONYMOUS";
return MECHANISM;
}
@Override

View file

@ -12,6 +12,9 @@ import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.TagWriter;
public class DigestMd5 extends SaslMechanism {
public static final String MECHANISM = "DIGEST-MD5";
public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@ -23,7 +26,7 @@ public class DigestMd5 extends SaslMechanism {
@Override
public String getMechanism() {
return "DIGEST-MD5";
return MECHANISM;
}
private State state = State.INITIAL;
@ -51,7 +54,7 @@ public class DigestMd5 extends SaslMechanism {
+ 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 String cNonce = CryptoHelper.random(100, rng);
final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
(":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
final String a2 = "AUTHENTICATE:" + digestUri;
@ -79,7 +82,7 @@ public class DigestMd5 extends SaslMechanism {
state = State.VALID_SERVER_RESPONSE;
break;
case VALID_SERVER_RESPONSE:
if (challenge==null) {
if (challenge == null) {
return null; //everything is fine
}
default:

View file

@ -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,6 +9,8 @@ import eu.siacs.conversations.xml.TagWriter;
public class External extends SaslMechanism {
public static final String MECHANISM = "EXTERNAL";
public External(TagWriter tagWriter, Account account, SecureRandom rng) {
super(tagWriter, account, rng);
}
@ -19,11 +22,11 @@ public class External extends SaslMechanism {
@Override
public String getMechanism() {
return "EXTERNAL";
return MECHANISM;
}
@Override
public String getClientFirstMessage() {
return Base64.encodeToString(account.getJid().asBareJid().toEscapedString().getBytes(),Base64.NO_WRAP);
return Base64.encodeToString(account.getJid().asBareJid().toEscapedString().getBytes(), Base64.NO_WRAP);
}
}

View file

@ -8,6 +8,9 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter;
public class Plain extends SaslMechanism {
public static final String MECHANISM = "PLAIN";
public Plain(final TagWriter tagWriter, final Account account) {
super(tagWriter, account, null);
}
@ -19,7 +22,7 @@ public class Plain extends SaslMechanism {
@Override
public String getMechanism() {
return "PLAIN";
return MECHANISM;
}
@Override

View file

@ -28,7 +28,7 @@ public abstract class SaslMechanism {
}
public AuthenticationException(final String message, final Exception exception) {
super(message,exception);
super(message, exception);
}
}
@ -52,14 +52,17 @@ public abstract class SaslMechanism {
* 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 "";
}

View file

@ -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,,";

View file

@ -11,6 +11,8 @@ import eu.siacs.conversations.xml.TagWriter;
public class ScramSha1 extends ScramMechanism {
public static final String MECHANISM = "SCRAM-SHA-1";
@Override
protected HMac getHMAC() {
return new HMac(new SHA1Digest());
@ -32,6 +34,6 @@ public class ScramSha1 extends ScramMechanism {
@Override
public String getMechanism() {
return "SCRAM-SHA-1";
return MECHANISM;
}
}

View file

@ -11,6 +11,8 @@ import eu.siacs.conversations.xml.TagWriter;
public class ScramSha256 extends ScramMechanism {
public static final String MECHANISM = "SCRAM-SHA-256";
@Override
protected HMac getHMAC() {
return new HMac(new SHA256Digest());
@ -32,6 +34,6 @@ public class ScramSha256 extends ScramMechanism {
@Override
public String getMechanism() {
return "SCRAM-SHA-256";
return MECHANISM;
}
}

View file

@ -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;
}
}

View file

@ -60,7 +60,7 @@ public final class Tokenizer implements Iterator<String>, Iterable<String> {
*/
@Override
public void remove() {
if(index <= 0) {
if (index <= 0) {
throw new IllegalStateException("You can't delete an element before first next() method call");
}
parts.remove(--index);

View file

@ -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) {
@ -1266,12 +1268,12 @@ public class XmppConnection implements Runnable {
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);
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()) {
for (final Element child : query.getChildren()) {
if ("item".equals(child.getName())) {
final String node = child.getAttribute("node");
final Jid jid = child.getAttributeAsJid("jid");
@ -1280,7 +1282,7 @@ public class XmppConnection implements Runnable {
}
}
}
Log.d(Config.LOGTAG,commands.toString());
Log.d(Config.LOGTAG, commands.toString());
synchronized (this.commands) {
this.commands.clear();
this.commands.putAll(commands);