refactor SASL choice into factory; remove unused TagWriter

This commit is contained in:
Daniel Gultsch 2022-09-06 09:25:09 +02:00
parent 511dfa13c4
commit a210568a9c
19 changed files with 288 additions and 231 deletions

View file

@ -1,5 +1,7 @@
package eu.siacs.conversations.crypto.axolotl;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.os.Bundle;
import android.security.KeyChain;
import android.util.Log;
@ -499,7 +501,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
Signature verifier = Signature.getInstance("sha256WithRSA");
verifier.initSign(x509PrivateKey, mXmppConnectionService.getRNG());
verifier.initSign(x509PrivateKey, SECURE_RANDOM);
verifier.update(axolotlPublicKey.serialize());
byte[] signature = verifier.sign();
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());

View file

@ -1,16 +1,13 @@
package eu.siacs.conversations.crypto.sasl;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
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);
public Anonymous(final Account account) {
super(account);
}
@Override

View file

@ -5,18 +5,17 @@ import android.util.Base64;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.TagWriter;
public class DigestMd5 extends SaslMechanism {
public static final String MECHANISM = "DIGEST-MD5";
private State state = State.INITIAL;
public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
public DigestMd5(final Account account) {
super(account);
}
@Override
@ -29,8 +28,6 @@ public class DigestMd5 extends SaslMechanism {
return MECHANISM;
}
private State state = State.INITIAL;
@Override
public String getResponse(final String challenge) throws AuthenticationException {
switch (state) {
@ -38,7 +35,8 @@ public class DigestMd5 extends SaslMechanism {
state = State.RESPONSE_SENT;
final String encodedResponse;
try {
final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
final Tokenizer tokenizer =
new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
String nonce = "";
for (final String token : tokenizer) {
final String[] parts = token.split("=", 2);
@ -50,29 +48,49 @@ public class DigestMd5 extends SaslMechanism {
}
final String digestUri = "xmpp/" + account.getServer();
final String nonceCount = "00000001";
final String x = account.getUsername() + ":" + account.getServer() + ":"
+ account.getPassword();
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 cNonce = CryptoHelper.random(100);
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);
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);
}
@ -83,7 +101,7 @@ public class DigestMd5 extends SaslMechanism {
break;
case VALID_SERVER_RESPONSE:
if (challenge == null) {
return null; //everything is fine
return null; // everything is fine
}
default:
throw new InvalidStateException(state);

View file

@ -2,17 +2,14 @@ package eu.siacs.conversations.crypto.sasl;
import android.util.Base64;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
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);
public External(final Account account) {
super(account);
}
@Override
@ -27,6 +24,7 @@ public class External extends SaslMechanism {
@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

@ -5,14 +5,18 @@ import android.util.Base64;
import java.nio.charset.Charset;
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);
public Plain(final Account account) {
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
@ -29,9 +33,4 @@ public class Plain extends SaslMechanism {
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);
}
}

View file

@ -2,18 +2,38 @@ package eu.siacs.conversations.crypto.sasl;
import com.google.common.base.Strings;
import java.security.SecureRandom;
import java.util.Collection;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xml.TagWriter;
public abstract class SaslMechanism {
final protected TagWriter tagWriter;
final protected Account account;
final protected SecureRandom rng;
protected final Account account;
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 {
INITIAL,
@ -22,6 +42,22 @@ public abstract class SaslMechanism {
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 AuthenticationException(final String message) {
super(message);
@ -46,42 +82,32 @@ public abstract class SaslMechanism {
}
}
public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
this.tagWriter = tagWriter;
this.account = account;
this.rng = rng;
}
public static final class Factory {
/**
* 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();
private final Account account;
public abstract String getMechanism();
public Factory(final Account account) {
this.account = account;
}
public String getClientFirstMessage() {
return "";
}
public String getResponse(final String challenge) throws AuthenticationException {
return "";
}
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 SaslMechanism of(final Collection<String> mechanisms) {
if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) {
return new External(account);
} else if (mechanisms.contains(ScramSha512.MECHANISM)) {
return new ScramSha512(account);
} else if (mechanisms.contains(ScramSha256.MECHANISM)) {
return new ScramSha256(account);
} else if (mechanisms.contains(ScramSha1.MECHANISM)) {
return new ScramSha1(account);
} else if (mechanisms.contains(Plain.MECHANISM)
&& !account.getServer().equals("nimbuzz.com")) {
return new Plain(account);
} else if (mechanisms.contains(DigestMd5.MECHANISM)) {
return new DigestMd5(account);
} else if (mechanisms.contains(Anonymous.MECHANISM)) {
return new Anonymous(account);
} else {
return null;
}
}
}

View file

@ -12,78 +12,53 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.util.concurrent.ExecutionException;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.TagWriter;
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,,";
// TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to
// 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[] SERVER_KEY_BYTES = "Server Key".getBytes();
protected abstract HMac getHMAC();
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 static final Cache<CacheKey, KeyPair> CACHE =
CacheBuilder.newBuilder().maximumSize(10).build();
private final String clientNonce;
protected State state = State.INITIAL;
private String clientFirstMessageBare;
private byte[] serverSignature = null;
ScramMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
ScramMechanism(final Account account) {
super(account);
// This nonce should be different for each authentication attempt.
clientNonce = CryptoHelper.random(100, rng);
this.clientNonce = CryptoHelper.random(100);
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 {
final HMac hMac = getHMAC();
hMac.init(new KeyParameter(key));
@ -123,8 +98,11 @@ abstract class ScramMechanism extends SaslMechanism {
@Override
public String getClientFirstMessage() {
if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) +
",r=" + this.clientNonce;
clientFirstMessageBare =
"n="
+ CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername()))
+ ",r="
+ this.clientNonce;
state = State.AUTH_TEXT_SENT;
}
return Base64.encodeToString(
@ -173,7 +151,8 @@ abstract class ScramMechanism extends SaslMechanism {
* MUST cause authentication failure when the attribute is parsed by
* 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");
}
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()) {
throw new AuthenticationException("Server sent empty salt");
}
final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString(
GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce;
final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
+ clientFinalMessageWithoutProof).getBytes();
final String clientFinalMessageWithoutProof =
"c="
+ Base64.encodeToString(GS2_HEADER.getBytes(), Base64.NO_WRAP)
+ ",r="
+ nonce;
final byte[] authMessage =
(clientFirstMessageBare
+ ','
+ new String(serverFirstMessage)
+ ','
+ clientFinalMessageWithoutProof)
.getBytes();
final KeyPair keys;
try {
keys = getKeyPair(CryptoHelper.saslPrep(account.getPassword()), salt, iterationCount);
keys =
getKeyPair(
CryptoHelper.saslPrep(account.getPassword()),
salt,
iterationCount);
} catch (ExecutionException e) {
throw new AuthenticationException("Invalid keys generated");
}
@ -213,35 +205,69 @@ abstract class ScramMechanism extends SaslMechanism {
final byte[] clientProof = new byte[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++) {
clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
}
final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" +
Base64.encodeToString(clientProof, Base64.NO_WRAP);
final String clientFinalMessage =
clientFinalMessageWithoutProof
+ ",p="
+ Base64.encodeToString(clientProof, Base64.NO_WRAP);
state = State.RESPONSE_SENT;
return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
case RESPONSE_SENT:
try {
final String clientCalculatedServerFinalMessage = "v=" +
Base64.encodeToString(serverSignature, Base64.NO_WRAP);
if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
final String clientCalculatedServerFinalMessage =
"v=" + Base64.encodeToString(serverSignature, Base64.NO_WRAP);
if (!clientCalculatedServerFinalMessage.equals(
new String(Base64.decode(challenge, Base64.DEFAULT)))) {
throw new Exception();
}
state = State.VALID_SERVER_RESPONSE;
return "";
} 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:
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 {
final byte[] clientKey;
final byte[] serverKey;

View file

@ -4,15 +4,16 @@ import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.macs.HMac;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter;
public class ScramSha1 extends ScramMechanism {
public static final String MECHANISM = "SCRAM-SHA-1";
public ScramSha1(final Account account) {
super(account);
}
@Override
protected HMac getHMAC() {
return new HMac(new SHA1Digest());
@ -23,10 +24,6 @@ public class ScramSha1 extends ScramMechanism {
return new SHA1Digest();
}
public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public int getPriority() {
return 20;

View file

@ -4,15 +4,16 @@ import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.macs.HMac;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter;
public class ScramSha256 extends ScramMechanism {
public static final String MECHANISM = "SCRAM-SHA-256";
public ScramSha256(final Account account) {
super(account);
}
@Override
protected HMac getHMAC() {
return new HMac(new SHA256Digest());
@ -23,10 +24,6 @@ public class ScramSha256 extends ScramMechanism {
return new SHA256Digest();
}
public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public int getPriority() {
return 25;

View file

@ -4,15 +4,16 @@ 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";
public ScramSha512(final Account account) {
super(account);
}
@Override
protected HMac getHMAC() {
return new HMac(new SHA512Digest());
@ -23,10 +24,6 @@ public class ScramSha512 extends ScramMechanism {
return new SHA512Digest();
}
public ScramSha512(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public int getPriority() {
return 30;

View file

@ -6,9 +6,7 @@ import java.util.Iterator;
import java.util.List;
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> {
private final List<String> parts;
private int index;
@ -50,18 +48,19 @@ public final class Tokenizer implements Iterator<String>, Iterable<String> {
}
/**
* Removes the last object returned by {@code next} from the collection.
* This method can only be called once between each call to {@code next}.
* 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}.
* 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");
throw new IllegalStateException(
"You can't delete an element before first next() method call");
}
parts.remove(--index);
}

View file

@ -1,5 +1,7 @@
package eu.siacs.conversations.http;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.os.Build;
import android.util.Log;
@ -147,7 +149,7 @@ public class HttpConnectionManager extends AbstractConnectionManager {
trustManager = mXmppConnectionService.getMemorizingTrustManager().getNonInteractive();
}
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.hostnameVerifier(new StrictHostnameVerifier());
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {

View file

@ -1,5 +1,7 @@
package eu.siacs.conversations.http;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.util.Log;
import androidx.annotation.NonNull;
@ -124,7 +126,7 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
this.key = new byte[44];
mXmppConnectionService.getRNG().nextBytes(this.key);
SECURE_RANDOM.nextBytes(this.key);
this.file.setKeyAndIv(this.key);
}
this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0));

View file

@ -1,5 +1,7 @@
package eu.siacs.conversations.services;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.util.Log;
import org.jetbrains.annotations.NotNull;
@ -502,7 +504,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
this.start = start.getTimestamp();
}
this.end = end;
this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32);
this.queryId = new BigInteger(50, SECURE_RANDOM).toString(32);
this.version = version;
}

View file

@ -1,6 +1,7 @@
package eu.siacs.conversations.services;
import static eu.siacs.conversations.utils.Compatibility.s;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.Manifest;
import android.annotation.SuppressLint;
@ -379,7 +380,6 @@ public class XmppConnectionService extends Service {
}
};
private final AtomicLong mLastExpiryRun = new AtomicLong(0);
private SecureRandom mRandom;
private final LruCache<Pair<String, String>, ServiceDiscoveryResult> discoCache = new LruCache<>(20);
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");
reconnectAccount(account, true, false);
} else {
int timeToReconnect = mRandom.nextInt(10) + 2;
final int timeToReconnect = SECURE_RANDOM.nextInt(10) + 2;
scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
}
} 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);
}
Resolver.init(this);
this.mRandom = new SecureRandom();
updateMemorizingTrustmanager();
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
@ -3269,7 +3268,7 @@ public class XmppConnectionService extends Service {
}
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);
joinMuc(conversation, new OnConferenceJoined() {
@Override
@ -4366,10 +4365,6 @@ public class XmppConnectionService extends Service {
}
}
public SecureRandom getRNG() {
return this.mRandom;
}
public MemorizingTrustManager getMemorizingTrustManager() {
return this.mMemorizingTrustManager;
}

View file

@ -43,7 +43,6 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
private boolean jidWasModified = false;
private boolean nameEntered = false;
private boolean skipTetxWatcher = false;
private static final SecureRandom RANDOM = new SecureRandom();
public static CreatePublicChannelDialog newInstance(List<String> accounts) {
CreatePublicChannelDialog dialog = new CreatePublicChannelDialog();
@ -158,7 +157,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
try {
return Jid.of(localpart, domain, null).toEscapedString();
} catch (IllegalArgumentException e) {
return Jid.of(CryptoHelper.pronounceable(RANDOM), domain, null).toEscapedString();
return Jid.of(CryptoHelper.pronounceable(), domain, null).toEscapedString();
}
}
}

View file

@ -1,5 +1,7 @@
package eu.siacs.conversations.utils;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.os.Bundle;
import android.util.Base64;
import android.util.Pair;
@ -59,12 +61,12 @@ public final class CryptoHelper {
return builder.toString();
}
public static String pronounceable(SecureRandom random) {
final int rand = random.nextInt(4);
public static String pronounceable() {
final int rand = SECURE_RANDOM.nextInt(4);
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) {
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;
}
return String.valueOf(output);
@ -117,9 +119,9 @@ public final class CryptoHelper {
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];
random.nextBytes(bytes);
SECURE_RANDOM.nextBytes(bytes);
return Base64.encodeToString(bytes, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE);
}

View 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() {
}
}

View file

@ -1,5 +1,7 @@
package eu.siacs.conversations.xmpp;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -521,7 +523,7 @@ public class XmppConnection implements Runnable {
? trustManager.getInteractive(domain)
: trustManager.getNonInteractive(domain)
},
mXmppConnectionService.getRNG());
SECURE_RANDOM);
return sc.getSocketFactory();
}
@ -1216,23 +1218,11 @@ public class XmppConnection implements Runnable {
}
private void authenticate(final SaslMechanism.Version version) throws IOException {
final List<String> mechanisms = extractMechanisms(streamFeatures.findChild("mechanisms"));
if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) {
saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG());
} 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(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());
}
final Element element = streamFeatures.findChild("mechanisms");
final Collection<String> mechanisms = Collections2.transform(element.getChildren(), c -> c == null ? null : c.getContent());
final SaslMechanism.Factory factory = new SaslMechanism.Factory(account);
this.saslMechanism = factory.of(mechanisms);
if (saslMechanism == null) {
Log.d(
Config.LOGTAG,
@ -1317,12 +1307,8 @@ public class XmppConnection implements Runnable {
return bind;
}
private static List<String> extractMechanisms(final Element stream) {
final ArrayList<String> mechanisms = new ArrayList<>(stream.getChildren().size());
for (final Element child : stream.getChildren()) {
mechanisms.add(child.getContent());
}
return mechanisms;
private static Collection<String> extractMechanisms(final Element stream) {
return Collections2.transform(stream.getChildren(), c -> c == null ? null : c.getContent());
}
private void register() {
@ -1963,8 +1949,8 @@ public class XmppConnection implements Runnable {
return nextRandomId(false);
}
private String nextRandomId(boolean s) {
return CryptoHelper.random(s ? 3 : 9, mXmppConnectionService.getRNG());
private String nextRandomId(final boolean s) {
return CryptoHelper.random(s ? 3 : 9);
}
public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {