refactor SASL choice into factory; remove unused TagWriter
This commit is contained in:
parent
511dfa13c4
commit
a210568a9c
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.crypto.axolotl;
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.security.KeyChain;
|
import android.security.KeyChain;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -499,7 +501,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
|
PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
|
||||||
X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
|
X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
|
||||||
Signature verifier = Signature.getInstance("sha256WithRSA");
|
Signature verifier = Signature.getInstance("sha256WithRSA");
|
||||||
verifier.initSign(x509PrivateKey, mXmppConnectionService.getRNG());
|
verifier.initSign(x509PrivateKey, SECURE_RANDOM);
|
||||||
verifier.update(axolotlPublicKey.serialize());
|
verifier.update(axolotlPublicKey.serialize());
|
||||||
byte[] signature = verifier.sign();
|
byte[] signature = verifier.sign();
|
||||||
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
|
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
package eu.siacs.conversations.crypto.sasl;
|
package eu.siacs.conversations.crypto.sasl;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
public class Anonymous extends SaslMechanism {
|
public class Anonymous extends SaslMechanism {
|
||||||
|
|
||||||
public static final String MECHANISM = "ANONYMOUS";
|
public static final String MECHANISM = "ANONYMOUS";
|
||||||
|
|
||||||
public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
|
public Anonymous(final Account account) {
|
||||||
super(tagWriter, account, rng);
|
super(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,18 +5,17 @@ import android.util.Base64;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
public class DigestMd5 extends SaslMechanism {
|
public class DigestMd5 extends SaslMechanism {
|
||||||
|
|
||||||
public static final String MECHANISM = "DIGEST-MD5";
|
public static final String MECHANISM = "DIGEST-MD5";
|
||||||
|
private State state = State.INITIAL;
|
||||||
|
|
||||||
public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
|
public DigestMd5(final Account account) {
|
||||||
super(tagWriter, account, rng);
|
super(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,8 +28,6 @@ public class DigestMd5 extends SaslMechanism {
|
||||||
return MECHANISM;
|
return MECHANISM;
|
||||||
}
|
}
|
||||||
|
|
||||||
private State state = State.INITIAL;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getResponse(final String challenge) throws AuthenticationException {
|
public String getResponse(final String challenge) throws AuthenticationException {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
@ -38,7 +35,8 @@ public class DigestMd5 extends SaslMechanism {
|
||||||
state = State.RESPONSE_SENT;
|
state = State.RESPONSE_SENT;
|
||||||
final String encodedResponse;
|
final String encodedResponse;
|
||||||
try {
|
try {
|
||||||
final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
|
final Tokenizer tokenizer =
|
||||||
|
new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
|
||||||
String nonce = "";
|
String nonce = "";
|
||||||
for (final String token : tokenizer) {
|
for (final String token : tokenizer) {
|
||||||
final String[] parts = token.split("=", 2);
|
final String[] parts = token.split("=", 2);
|
||||||
|
@ -50,29 +48,49 @@ public class DigestMd5 extends SaslMechanism {
|
||||||
}
|
}
|
||||||
final String digestUri = "xmpp/" + account.getServer();
|
final String digestUri = "xmpp/" + account.getServer();
|
||||||
final String nonceCount = "00000001";
|
final String nonceCount = "00000001";
|
||||||
final String x = account.getUsername() + ":" + account.getServer() + ":"
|
final String x =
|
||||||
|
account.getUsername()
|
||||||
|
+ ":"
|
||||||
|
+ account.getServer()
|
||||||
|
+ ":"
|
||||||
+ account.getPassword();
|
+ account.getPassword();
|
||||||
final MessageDigest md = MessageDigest.getInstance("MD5");
|
final MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
|
final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
|
||||||
final String cNonce = CryptoHelper.random(100, rng);
|
final String cNonce = CryptoHelper.random(100);
|
||||||
final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
|
final byte[] a1 =
|
||||||
(":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
|
CryptoHelper.concatenateByteArrays(
|
||||||
|
y,
|
||||||
|
(":" + nonce + ":" + cNonce)
|
||||||
|
.getBytes(Charset.defaultCharset()));
|
||||||
final String a2 = "AUTHENTICATE:" + digestUri;
|
final String a2 = "AUTHENTICATE:" + digestUri;
|
||||||
final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
|
final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
|
||||||
final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
|
final String ha2 =
|
||||||
.defaultCharset())));
|
CryptoHelper.bytesToHex(
|
||||||
final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
|
md.digest(a2.getBytes(Charset.defaultCharset())));
|
||||||
+ ":auth:" + ha2;
|
final String kd =
|
||||||
final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
|
ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce + ":auth:" + ha2;
|
||||||
.defaultCharset())));
|
final String response =
|
||||||
final String saslString = "username=\"" + account.getUsername()
|
CryptoHelper.bytesToHex(
|
||||||
+ "\",realm=\"" + account.getServer() + "\",nonce=\""
|
md.digest(kd.getBytes(Charset.defaultCharset())));
|
||||||
+ nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
|
final String saslString =
|
||||||
+ ",qop=auth,digest-uri=\"" + digestUri + "\",response="
|
"username=\""
|
||||||
+ response + ",charset=utf-8";
|
+ account.getUsername()
|
||||||
encodedResponse = Base64.encodeToString(
|
+ "\",realm=\""
|
||||||
saslString.getBytes(Charset.defaultCharset()),
|
+ account.getServer()
|
||||||
Base64.NO_WRAP);
|
+ "\",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) {
|
} catch (final NoSuchAlgorithmException e) {
|
||||||
throw new AuthenticationException(e);
|
throw new AuthenticationException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,14 @@ package eu.siacs.conversations.crypto.sasl;
|
||||||
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
public class External extends SaslMechanism {
|
public class External extends SaslMechanism {
|
||||||
|
|
||||||
public static final String MECHANISM = "EXTERNAL";
|
public static final String MECHANISM = "EXTERNAL";
|
||||||
|
|
||||||
public External(TagWriter tagWriter, Account account, SecureRandom rng) {
|
public External(final Account account) {
|
||||||
super(tagWriter, account, rng);
|
super(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -27,6 +24,7 @@ public class External extends SaslMechanism {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientFirstMessage() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,18 @@ import android.util.Base64;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
public class Plain extends SaslMechanism {
|
public class Plain extends SaslMechanism {
|
||||||
|
|
||||||
public static final String MECHANISM = "PLAIN";
|
public static final String MECHANISM = "PLAIN";
|
||||||
|
|
||||||
public Plain(final TagWriter tagWriter, final Account account) {
|
public Plain(final Account account) {
|
||||||
super(tagWriter, account, null);
|
super(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,9 +33,4 @@ public class Plain extends SaslMechanism {
|
||||||
public String getClientFirstMessage() {
|
public String getClientFirstMessage() {
|
||||||
return getMessage(account.getUsername(), account.getPassword());
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,38 @@ package eu.siacs.conversations.crypto.sasl;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.util.Collection;
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
public abstract class SaslMechanism {
|
public abstract class SaslMechanism {
|
||||||
|
|
||||||
final protected TagWriter tagWriter;
|
protected final Account account;
|
||||||
final protected Account account;
|
|
||||||
final protected SecureRandom rng;
|
protected SaslMechanism(final Account account) {
|
||||||
|
this.account = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 "";
|
||||||
|
}
|
||||||
|
|
||||||
protected enum State {
|
protected enum State {
|
||||||
INITIAL,
|
INITIAL,
|
||||||
|
@ -22,6 +42,22 @@ public abstract class SaslMechanism {
|
||||||
VALID_SERVER_RESPONSE,
|
VALID_SERVER_RESPONSE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Version {
|
||||||
|
SASL,
|
||||||
|
SASL_2;
|
||||||
|
|
||||||
|
public static Version of(final Element element) {
|
||||||
|
switch (Strings.nullToEmpty(element.getNamespace())) {
|
||||||
|
case Namespace.SASL:
|
||||||
|
return SASL;
|
||||||
|
case Namespace.SASL_2:
|
||||||
|
return SASL_2;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unrecognized SASL namespace");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class AuthenticationException extends Exception {
|
public static class AuthenticationException extends Exception {
|
||||||
public AuthenticationException(final String message) {
|
public AuthenticationException(final String message) {
|
||||||
super(message);
|
super(message);
|
||||||
|
@ -46,42 +82,32 @@ public abstract class SaslMechanism {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
|
public static final class Factory {
|
||||||
this.tagWriter = tagWriter;
|
|
||||||
|
private final Account account;
|
||||||
|
|
||||||
|
public Factory(final Account account) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.rng = rng;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public SaslMechanism of(final Collection<String> mechanisms) {
|
||||||
* The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
|
if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) {
|
||||||
* mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
|
return new External(account);
|
||||||
* attacks).
|
} else if (mechanisms.contains(ScramSha512.MECHANISM)) {
|
||||||
*
|
return new ScramSha512(account);
|
||||||
* @return An arbitrary int representing the priority
|
} else if (mechanisms.contains(ScramSha256.MECHANISM)) {
|
||||||
*/
|
return new ScramSha256(account);
|
||||||
public abstract int getPriority();
|
} else if (mechanisms.contains(ScramSha1.MECHANISM)) {
|
||||||
|
return new ScramSha1(account);
|
||||||
public abstract String getMechanism();
|
} else if (mechanisms.contains(Plain.MECHANISM)
|
||||||
|
&& !account.getServer().equals("nimbuzz.com")) {
|
||||||
public String getClientFirstMessage() {
|
return new Plain(account);
|
||||||
return "";
|
} else if (mechanisms.contains(DigestMd5.MECHANISM)) {
|
||||||
}
|
return new DigestMd5(account);
|
||||||
|
} else if (mechanisms.contains(Anonymous.MECHANISM)) {
|
||||||
public String getResponse(final String challenge) throws AuthenticationException {
|
return new Anonymous(account);
|
||||||
return "";
|
} else {
|
||||||
}
|
return null;
|
||||||
|
|
||||||
public enum Version {
|
|
||||||
SASL, SASL_2;
|
|
||||||
|
|
||||||
public static Version of(final Element element) {
|
|
||||||
switch ( Strings.nullToEmpty(element.getNamespace())) {
|
|
||||||
case Namespace.SASL:
|
|
||||||
return SASL;
|
|
||||||
case Namespace.SASL_2:
|
|
||||||
return SASL_2;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized SASL namespace");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,78 +12,53 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
abstract class ScramMechanism extends SaslMechanism {
|
abstract class ScramMechanism extends SaslMechanism {
|
||||||
// TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
|
// TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to
|
||||||
private final static String GS2_HEADER = "n,,";
|
// indicate support and/or usage.
|
||||||
|
private static final String GS2_HEADER = "n,,";
|
||||||
private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
|
private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
|
||||||
private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
|
private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
|
||||||
|
private static final Cache<CacheKey, KeyPair> CACHE =
|
||||||
protected abstract HMac getHMAC();
|
CacheBuilder.newBuilder().maximumSize(10).build();
|
||||||
|
|
||||||
protected abstract Digest getDigest();
|
|
||||||
|
|
||||||
private static final Cache<CacheKey, KeyPair> CACHE = CacheBuilder.newBuilder().maximumSize(10).build();
|
|
||||||
|
|
||||||
private static class CacheKey {
|
|
||||||
final String algorithm;
|
|
||||||
final String password;
|
|
||||||
final String salt;
|
|
||||||
final int iterations;
|
|
||||||
|
|
||||||
private CacheKey(String algorithm, String password, String salt, int iterations) {
|
|
||||||
this.algorithm = algorithm;
|
|
||||||
this.password = password;
|
|
||||||
this.salt = salt;
|
|
||||||
this.iterations = iterations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
CacheKey cacheKey = (CacheKey) o;
|
|
||||||
return iterations == cacheKey.iterations &&
|
|
||||||
Objects.equal(algorithm, cacheKey.algorithm) &&
|
|
||||||
Objects.equal(password, cacheKey.password) &&
|
|
||||||
Objects.equal(salt, cacheKey.salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(algorithm, password, salt, iterations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyPair getKeyPair(final String password, final String salt, final int iterations) throws ExecutionException {
|
|
||||||
return CACHE.get(new CacheKey(getHMAC().getAlgorithmName(), password, salt, iterations), () -> {
|
|
||||||
final byte[] saltedPassword, serverKey, clientKey;
|
|
||||||
saltedPassword = hi(password.getBytes(), Base64.decode(salt, Base64.DEFAULT), iterations);
|
|
||||||
serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
|
|
||||||
clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
|
|
||||||
return new KeyPair(clientKey, serverKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String clientNonce;
|
private final String clientNonce;
|
||||||
protected State state = State.INITIAL;
|
protected State state = State.INITIAL;
|
||||||
private String clientFirstMessageBare;
|
private String clientFirstMessageBare;
|
||||||
private byte[] serverSignature = null;
|
private byte[] serverSignature = null;
|
||||||
|
|
||||||
ScramMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
|
ScramMechanism(final Account account) {
|
||||||
super(tagWriter, account, rng);
|
super(account);
|
||||||
|
|
||||||
// This nonce should be different for each authentication attempt.
|
// This nonce should be different for each authentication attempt.
|
||||||
clientNonce = CryptoHelper.random(100, rng);
|
this.clientNonce = CryptoHelper.random(100);
|
||||||
clientFirstMessageBare = "";
|
clientFirstMessageBare = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract HMac getHMAC();
|
||||||
|
|
||||||
|
protected abstract Digest getDigest();
|
||||||
|
|
||||||
|
private KeyPair getKeyPair(final String password, final String salt, final int iterations)
|
||||||
|
throws ExecutionException {
|
||||||
|
return CACHE.get(
|
||||||
|
new CacheKey(getHMAC().getAlgorithmName(), password, salt, iterations),
|
||||||
|
() -> {
|
||||||
|
final byte[] saltedPassword, serverKey, clientKey;
|
||||||
|
saltedPassword =
|
||||||
|
hi(
|
||||||
|
password.getBytes(),
|
||||||
|
Base64.decode(salt, Base64.DEFAULT),
|
||||||
|
iterations);
|
||||||
|
serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
|
||||||
|
clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
|
||||||
|
return new KeyPair(clientKey, serverKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] hmac(final byte[] key, final byte[] input) throws InvalidKeyException {
|
private byte[] hmac(final byte[] key, final byte[] input) throws InvalidKeyException {
|
||||||
final HMac hMac = getHMAC();
|
final HMac hMac = getHMAC();
|
||||||
hMac.init(new KeyParameter(key));
|
hMac.init(new KeyParameter(key));
|
||||||
|
@ -123,8 +98,11 @@ abstract class ScramMechanism extends SaslMechanism {
|
||||||
@Override
|
@Override
|
||||||
public String getClientFirstMessage() {
|
public String getClientFirstMessage() {
|
||||||
if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
|
if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
|
||||||
clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) +
|
clientFirstMessageBare =
|
||||||
",r=" + this.clientNonce;
|
"n="
|
||||||
|
+ CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername()))
|
||||||
|
+ ",r="
|
||||||
|
+ this.clientNonce;
|
||||||
state = State.AUTH_TEXT_SENT;
|
state = State.AUTH_TEXT_SENT;
|
||||||
}
|
}
|
||||||
return Base64.encodeToString(
|
return Base64.encodeToString(
|
||||||
|
@ -173,7 +151,8 @@ abstract class ScramMechanism extends SaslMechanism {
|
||||||
* MUST cause authentication failure when the attribute is parsed by
|
* MUST cause authentication failure when the attribute is parsed by
|
||||||
* the other end.
|
* the other end.
|
||||||
*/
|
*/
|
||||||
throw new AuthenticationException("Server sent reserved token: `m'");
|
throw new AuthenticationException(
|
||||||
|
"Server sent reserved token: `m'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,20 +161,33 @@ abstract class ScramMechanism extends SaslMechanism {
|
||||||
throw new AuthenticationException("Server did not send iteration count");
|
throw new AuthenticationException("Server did not send iteration count");
|
||||||
}
|
}
|
||||||
if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
|
if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
|
||||||
throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce);
|
throw new AuthenticationException(
|
||||||
|
"Server nonce does not contain client nonce: " + nonce);
|
||||||
}
|
}
|
||||||
if (salt.isEmpty()) {
|
if (salt.isEmpty()) {
|
||||||
throw new AuthenticationException("Server sent empty salt");
|
throw new AuthenticationException("Server sent empty salt");
|
||||||
}
|
}
|
||||||
|
|
||||||
final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString(
|
final String clientFinalMessageWithoutProof =
|
||||||
GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce;
|
"c="
|
||||||
final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
|
+ Base64.encodeToString(GS2_HEADER.getBytes(), Base64.NO_WRAP)
|
||||||
+ clientFinalMessageWithoutProof).getBytes();
|
+ ",r="
|
||||||
|
+ nonce;
|
||||||
|
final byte[] authMessage =
|
||||||
|
(clientFirstMessageBare
|
||||||
|
+ ','
|
||||||
|
+ new String(serverFirstMessage)
|
||||||
|
+ ','
|
||||||
|
+ clientFinalMessageWithoutProof)
|
||||||
|
.getBytes();
|
||||||
|
|
||||||
final KeyPair keys;
|
final KeyPair keys;
|
||||||
try {
|
try {
|
||||||
keys = getKeyPair(CryptoHelper.saslPrep(account.getPassword()), salt, iterationCount);
|
keys =
|
||||||
|
getKeyPair(
|
||||||
|
CryptoHelper.saslPrep(account.getPassword()),
|
||||||
|
salt,
|
||||||
|
iterationCount);
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
throw new AuthenticationException("Invalid keys generated");
|
throw new AuthenticationException("Invalid keys generated");
|
||||||
}
|
}
|
||||||
|
@ -213,35 +205,69 @@ abstract class ScramMechanism extends SaslMechanism {
|
||||||
final byte[] clientProof = new byte[keys.clientKey.length];
|
final byte[] clientProof = new byte[keys.clientKey.length];
|
||||||
|
|
||||||
if (clientSignature.length < keys.clientKey.length) {
|
if (clientSignature.length < keys.clientKey.length) {
|
||||||
throw new AuthenticationException("client signature was shorter than clientKey");
|
throw new AuthenticationException(
|
||||||
|
"client signature was shorter than clientKey");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < clientProof.length; i++) {
|
for (int i = 0; i < clientProof.length; i++) {
|
||||||
clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
|
clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String clientFinalMessage =
|
||||||
final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" +
|
clientFinalMessageWithoutProof
|
||||||
Base64.encodeToString(clientProof, Base64.NO_WRAP);
|
+ ",p="
|
||||||
|
+ Base64.encodeToString(clientProof, Base64.NO_WRAP);
|
||||||
state = State.RESPONSE_SENT;
|
state = State.RESPONSE_SENT;
|
||||||
return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
|
return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
|
||||||
case RESPONSE_SENT:
|
case RESPONSE_SENT:
|
||||||
try {
|
try {
|
||||||
final String clientCalculatedServerFinalMessage = "v=" +
|
final String clientCalculatedServerFinalMessage =
|
||||||
Base64.encodeToString(serverSignature, Base64.NO_WRAP);
|
"v=" + Base64.encodeToString(serverSignature, Base64.NO_WRAP);
|
||||||
if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
|
if (!clientCalculatedServerFinalMessage.equals(
|
||||||
|
new String(Base64.decode(challenge, Base64.DEFAULT)))) {
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
}
|
}
|
||||||
state = State.VALID_SERVER_RESPONSE;
|
state = State.VALID_SERVER_RESPONSE;
|
||||||
return "";
|
return "";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new AuthenticationException("Server final message does not match calculated final message");
|
throw new AuthenticationException(
|
||||||
|
"Server final message does not match calculated final message");
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new InvalidStateException(state);
|
throw new InvalidStateException(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CacheKey {
|
||||||
|
final String algorithm;
|
||||||
|
final String password;
|
||||||
|
final String salt;
|
||||||
|
final int iterations;
|
||||||
|
|
||||||
|
private CacheKey(String algorithm, String password, String salt, int iterations) {
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
this.password = password;
|
||||||
|
this.salt = salt;
|
||||||
|
this.iterations = iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
CacheKey cacheKey = (CacheKey) o;
|
||||||
|
return iterations == cacheKey.iterations
|
||||||
|
&& Objects.equal(algorithm, cacheKey.algorithm)
|
||||||
|
&& Objects.equal(password, cacheKey.password)
|
||||||
|
&& Objects.equal(salt, cacheKey.salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(algorithm, password, salt, iterations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class KeyPair {
|
private static class KeyPair {
|
||||||
final byte[] clientKey;
|
final byte[] clientKey;
|
||||||
final byte[] serverKey;
|
final byte[] serverKey;
|
||||||
|
|
|
@ -4,15 +4,16 @@ import org.bouncycastle.crypto.Digest;
|
||||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||||
import org.bouncycastle.crypto.macs.HMac;
|
import org.bouncycastle.crypto.macs.HMac;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
public class ScramSha1 extends ScramMechanism {
|
public class ScramSha1 extends ScramMechanism {
|
||||||
|
|
||||||
public static final String MECHANISM = "SCRAM-SHA-1";
|
public static final String MECHANISM = "SCRAM-SHA-1";
|
||||||
|
|
||||||
|
public ScramSha1(final Account account) {
|
||||||
|
super(account);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HMac getHMAC() {
|
protected HMac getHMAC() {
|
||||||
return new HMac(new SHA1Digest());
|
return new HMac(new SHA1Digest());
|
||||||
|
@ -23,10 +24,6 @@ public class ScramSha1 extends ScramMechanism {
|
||||||
return new SHA1Digest();
|
return new SHA1Digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
|
|
||||||
super(tagWriter, account, rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPriority() {
|
public int getPriority() {
|
||||||
return 20;
|
return 20;
|
||||||
|
|
|
@ -4,15 +4,16 @@ import org.bouncycastle.crypto.Digest;
|
||||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||||
import org.bouncycastle.crypto.macs.HMac;
|
import org.bouncycastle.crypto.macs.HMac;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
public class ScramSha256 extends ScramMechanism {
|
public class ScramSha256 extends ScramMechanism {
|
||||||
|
|
||||||
public static final String MECHANISM = "SCRAM-SHA-256";
|
public static final String MECHANISM = "SCRAM-SHA-256";
|
||||||
|
|
||||||
|
public ScramSha256(final Account account) {
|
||||||
|
super(account);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HMac getHMAC() {
|
protected HMac getHMAC() {
|
||||||
return new HMac(new SHA256Digest());
|
return new HMac(new SHA256Digest());
|
||||||
|
@ -23,10 +24,6 @@ public class ScramSha256 extends ScramMechanism {
|
||||||
return new SHA256Digest();
|
return new SHA256Digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
|
|
||||||
super(tagWriter, account, rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPriority() {
|
public int getPriority() {
|
||||||
return 25;
|
return 25;
|
||||||
|
|
|
@ -4,15 +4,16 @@ import org.bouncycastle.crypto.Digest;
|
||||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||||
import org.bouncycastle.crypto.macs.HMac;
|
import org.bouncycastle.crypto.macs.HMac;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.xml.TagWriter;
|
|
||||||
|
|
||||||
public class ScramSha512 extends ScramMechanism {
|
public class ScramSha512 extends ScramMechanism {
|
||||||
|
|
||||||
public static final String MECHANISM = "SCRAM-SHA-512";
|
public static final String MECHANISM = "SCRAM-SHA-512";
|
||||||
|
|
||||||
|
public ScramSha512(final Account account) {
|
||||||
|
super(account);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HMac getHMAC() {
|
protected HMac getHMAC() {
|
||||||
return new HMac(new SHA512Digest());
|
return new HMac(new SHA512Digest());
|
||||||
|
@ -23,10 +24,6 @@ public class ScramSha512 extends ScramMechanism {
|
||||||
return new SHA512Digest();
|
return new SHA512Digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScramSha512(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
|
|
||||||
super(tagWriter, account, rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPriority() {
|
public int getPriority() {
|
||||||
return 30;
|
return 30;
|
||||||
|
|
|
@ -6,9 +6,7 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
/**
|
/** A tokenizer for GS2 header strings */
|
||||||
* A tokenizer for GS2 header strings
|
|
||||||
*/
|
|
||||||
public final class Tokenizer implements Iterator<String>, Iterable<String> {
|
public final class Tokenizer implements Iterator<String>, Iterable<String> {
|
||||||
private final List<String> parts;
|
private final List<String> parts;
|
||||||
private int index;
|
private int index;
|
||||||
|
@ -50,8 +48,8 @@ public final class Tokenizer implements Iterator<String>, Iterable<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the last object returned by {@code next} from the collection.
|
* Removes the last object returned by {@code next} from the collection. This method can only be
|
||||||
* This method can only be called once between each call to {@code next}.
|
* called once between each call to {@code next}.
|
||||||
*
|
*
|
||||||
* @throws UnsupportedOperationException if removing is not supported by the collection being
|
* @throws UnsupportedOperationException if removing is not supported by the collection being
|
||||||
* iterated.
|
* iterated.
|
||||||
|
@ -61,7 +59,8 @@ public final class Tokenizer implements Iterator<String>, Iterable<String> {
|
||||||
@Override
|
@Override
|
||||||
public void remove() {
|
public void remove() {
|
||||||
if (index <= 0) {
|
if (index <= 0) {
|
||||||
throw new IllegalStateException("You can't delete an element before first next() method call");
|
throw new IllegalStateException(
|
||||||
|
"You can't delete an element before first next() method call");
|
||||||
}
|
}
|
||||||
parts.remove(--index);
|
parts.remove(--index);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.http;
|
package eu.siacs.conversations.http;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
@ -147,7 +149,7 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
||||||
trustManager = mXmppConnectionService.getMemorizingTrustManager().getNonInteractive();
|
trustManager = mXmppConnectionService.getMemorizingTrustManager().getNonInteractive();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG());
|
final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, SECURE_RANDOM);
|
||||||
builder.sslSocketFactory(sf, trustManager);
|
builder.sslSocketFactory(sf, trustManager);
|
||||||
builder.hostnameVerifier(new StrictHostnameVerifier());
|
builder.hostnameVerifier(new StrictHostnameVerifier());
|
||||||
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
|
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.http;
|
package eu.siacs.conversations.http;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -124,7 +126,7 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
|
||||||
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|
||||||
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
this.key = new byte[44];
|
this.key = new byte[44];
|
||||||
mXmppConnectionService.getRNG().nextBytes(this.key);
|
SECURE_RANDOM.nextBytes(this.key);
|
||||||
this.file.setKeyAndIv(this.key);
|
this.file.setKeyAndIv(this.key);
|
||||||
}
|
}
|
||||||
this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0));
|
this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0));
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
@ -502,7 +504,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
this.start = start.getTimestamp();
|
this.start = start.getTimestamp();
|
||||||
}
|
}
|
||||||
this.end = end;
|
this.end = end;
|
||||||
this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32);
|
this.queryId = new BigInteger(50, SECURE_RANDOM).toString(32);
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
import static eu.siacs.conversations.utils.Compatibility.s;
|
import static eu.siacs.conversations.utils.Compatibility.s;
|
||||||
|
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
@ -379,7 +380,6 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final AtomicLong mLastExpiryRun = new AtomicLong(0);
|
private final AtomicLong mLastExpiryRun = new AtomicLong(0);
|
||||||
private SecureRandom mRandom;
|
|
||||||
private final LruCache<Pair<String, String>, ServiceDiscoveryResult> discoCache = new LruCache<>(20);
|
private final LruCache<Pair<String, String>, ServiceDiscoveryResult> discoCache = new LruCache<>(20);
|
||||||
private final OnStatusChanged statusListener = new OnStatusChanged() {
|
private final OnStatusChanged statusListener = new OnStatusChanged() {
|
||||||
|
|
||||||
|
@ -451,7 +451,7 @@ public class XmppConnectionService extends Service {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": went into offline state during low ping mode. reconnecting now");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": went into offline state during low ping mode. reconnecting now");
|
||||||
reconnectAccount(account, true, false);
|
reconnectAccount(account, true, false);
|
||||||
} else {
|
} else {
|
||||||
int timeToReconnect = mRandom.nextInt(10) + 2;
|
final int timeToReconnect = SECURE_RANDOM.nextInt(10) + 2;
|
||||||
scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
|
scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
|
||||||
}
|
}
|
||||||
} else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
|
} else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
|
||||||
|
@ -1143,7 +1143,6 @@ public class XmppConnectionService extends Service {
|
||||||
Log.e(Config.LOGTAG, "unable to initialize security provider", throwable);
|
Log.e(Config.LOGTAG, "unable to initialize security provider", throwable);
|
||||||
}
|
}
|
||||||
Resolver.init(this);
|
Resolver.init(this);
|
||||||
this.mRandom = new SecureRandom();
|
|
||||||
updateMemorizingTrustmanager();
|
updateMemorizingTrustmanager();
|
||||||
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
||||||
final int cacheSize = maxMemory / 8;
|
final int cacheSize = maxMemory / 8;
|
||||||
|
@ -3269,7 +3268,7 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final Jid jid = Jid.of(CryptoHelper.pronounceable(getRNG()), server, null);
|
final Jid jid = Jid.of(CryptoHelper.pronounceable(), server, null);
|
||||||
final Conversation conversation = findOrCreateConversation(account, jid, true, false, true);
|
final Conversation conversation = findOrCreateConversation(account, jid, true, false, true);
|
||||||
joinMuc(conversation, new OnConferenceJoined() {
|
joinMuc(conversation, new OnConferenceJoined() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -4366,10 +4365,6 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecureRandom getRNG() {
|
|
||||||
return this.mRandom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MemorizingTrustManager getMemorizingTrustManager() {
|
public MemorizingTrustManager getMemorizingTrustManager() {
|
||||||
return this.mMemorizingTrustManager;
|
return this.mMemorizingTrustManager;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,6 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
|
||||||
private boolean jidWasModified = false;
|
private boolean jidWasModified = false;
|
||||||
private boolean nameEntered = false;
|
private boolean nameEntered = false;
|
||||||
private boolean skipTetxWatcher = false;
|
private boolean skipTetxWatcher = false;
|
||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
|
||||||
|
|
||||||
public static CreatePublicChannelDialog newInstance(List<String> accounts) {
|
public static CreatePublicChannelDialog newInstance(List<String> accounts) {
|
||||||
CreatePublicChannelDialog dialog = new CreatePublicChannelDialog();
|
CreatePublicChannelDialog dialog = new CreatePublicChannelDialog();
|
||||||
|
@ -158,7 +157,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
|
||||||
try {
|
try {
|
||||||
return Jid.of(localpart, domain, null).toEscapedString();
|
return Jid.of(localpart, domain, null).toEscapedString();
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
return Jid.of(CryptoHelper.pronounceable(RANDOM), domain, null).toEscapedString();
|
return Jid.of(CryptoHelper.pronounceable(), domain, null).toEscapedString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.utils;
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
@ -59,12 +61,12 @@ public final class CryptoHelper {
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String pronounceable(SecureRandom random) {
|
public static String pronounceable() {
|
||||||
final int rand = random.nextInt(4);
|
final int rand = SECURE_RANDOM.nextInt(4);
|
||||||
char[] output = new char[rand * 2 + (5 - rand)];
|
char[] output = new char[rand * 2 + (5 - rand)];
|
||||||
boolean vowel = random.nextBoolean();
|
boolean vowel = SECURE_RANDOM.nextBoolean();
|
||||||
for (int i = 0; i < output.length; ++i) {
|
for (int i = 0; i < output.length; ++i) {
|
||||||
output[i] = vowel ? VOWELS[random.nextInt(VOWELS.length)] : CONSONANTS[random.nextInt(CONSONANTS.length)];
|
output[i] = vowel ? VOWELS[SECURE_RANDOM.nextInt(VOWELS.length)] : CONSONANTS[SECURE_RANDOM.nextInt(CONSONANTS.length)];
|
||||||
vowel = !vowel;
|
vowel = !vowel;
|
||||||
}
|
}
|
||||||
return String.valueOf(output);
|
return String.valueOf(output);
|
||||||
|
@ -117,9 +119,9 @@ public final class CryptoHelper {
|
||||||
return Normalizer.normalize(s, Normalizer.Form.NFKC);
|
return Normalizer.normalize(s, Normalizer.Form.NFKC);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String random(int length, SecureRandom random) {
|
public static String random(final int length) {
|
||||||
final byte[] bytes = new byte[length];
|
final byte[] bytes = new byte[length];
|
||||||
random.nextBytes(bytes);
|
SECURE_RANDOM.nextBytes(bytes);
|
||||||
return Base64.encodeToString(bytes, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE);
|
return Base64.encodeToString(bytes, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
src/main/java/eu/siacs/conversations/utils/Random.java
Normal file
13
src/main/java/eu/siacs/conversations/utils/Random.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public final class Random {
|
||||||
|
|
||||||
|
public static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||||
|
|
||||||
|
private Random() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.xmpp;
|
package eu.siacs.conversations.xmpp;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
@ -521,7 +523,7 @@ public class XmppConnection implements Runnable {
|
||||||
? trustManager.getInteractive(domain)
|
? trustManager.getInteractive(domain)
|
||||||
: trustManager.getNonInteractive(domain)
|
: trustManager.getNonInteractive(domain)
|
||||||
},
|
},
|
||||||
mXmppConnectionService.getRNG());
|
SECURE_RANDOM);
|
||||||
return sc.getSocketFactory();
|
return sc.getSocketFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1216,23 +1218,11 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void authenticate(final SaslMechanism.Version version) throws IOException {
|
private void authenticate(final SaslMechanism.Version version) throws IOException {
|
||||||
final List<String> mechanisms = extractMechanisms(streamFeatures.findChild("mechanisms"));
|
final Element element = streamFeatures.findChild("mechanisms");
|
||||||
if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) {
|
final Collection<String> mechanisms = Collections2.transform(element.getChildren(), c -> c == null ? null : c.getContent());
|
||||||
saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG());
|
final SaslMechanism.Factory factory = new SaslMechanism.Factory(account);
|
||||||
} else if (mechanisms.contains(ScramSha512.MECHANISM)) {
|
this.saslMechanism = factory.of(mechanisms);
|
||||||
saslMechanism = new ScramSha512(tagWriter, account, mXmppConnectionService.getRNG());
|
|
||||||
} else if (mechanisms.contains(ScramSha256.MECHANISM)) {
|
|
||||||
saslMechanism = new ScramSha256(tagWriter, account, mXmppConnectionService.getRNG());
|
|
||||||
} else if (mechanisms.contains(ScramSha1.MECHANISM)) {
|
|
||||||
saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG());
|
|
||||||
} else if (mechanisms.contains(Plain.MECHANISM)
|
|
||||||
&& !account.getJid().getDomain().toEscapedString().equals("nimbuzz.com")) {
|
|
||||||
saslMechanism = new Plain(tagWriter, account);
|
|
||||||
} else if (mechanisms.contains(DigestMd5.MECHANISM)) {
|
|
||||||
saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG());
|
|
||||||
} else if (mechanisms.contains(Anonymous.MECHANISM)) {
|
|
||||||
saslMechanism = new Anonymous(tagWriter, account, mXmppConnectionService.getRNG());
|
|
||||||
}
|
|
||||||
if (saslMechanism == null) {
|
if (saslMechanism == null) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -1317,12 +1307,8 @@ public class XmppConnection implements Runnable {
|
||||||
return bind;
|
return bind;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> extractMechanisms(final Element stream) {
|
private static Collection<String> extractMechanisms(final Element stream) {
|
||||||
final ArrayList<String> mechanisms = new ArrayList<>(stream.getChildren().size());
|
return Collections2.transform(stream.getChildren(), c -> c == null ? null : c.getContent());
|
||||||
for (final Element child : stream.getChildren()) {
|
|
||||||
mechanisms.add(child.getContent());
|
|
||||||
}
|
|
||||||
return mechanisms;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void register() {
|
private void register() {
|
||||||
|
@ -1963,8 +1949,8 @@ public class XmppConnection implements Runnable {
|
||||||
return nextRandomId(false);
|
return nextRandomId(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String nextRandomId(boolean s) {
|
private String nextRandomId(final boolean s) {
|
||||||
return CryptoHelper.random(s ? 3 : 9, mXmppConnectionService.getRNG());
|
return CryptoHelper.random(s ? 3 : 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
|
public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
|
||||||
|
|
Loading…
Reference in a new issue