move SSLSockets helper library into im.conversations package

This commit is contained in:
Daniel Gultsch 2023-02-16 12:03:38 +01:00
parent 5866974eff
commit 7d34c894d0
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
8 changed files with 239 additions and 25 deletions

View file

@ -1,9 +1,16 @@
package im.conversations.android;
import android.app.Application;
import android.util.Log;
import com.google.android.material.color.DynamicColors;
import eu.siacs.conversations.Config;
import im.conversations.android.xmpp.ConnectionPool;
import java.security.SecureRandom;
import java.security.Security;
import org.conscrypt.Conscrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -16,6 +23,11 @@ public class Conversations extends Application {
@Override
public void onCreate() {
super.onCreate();
try {
Security.insertProviderAt(Conscrypt.newProvider(), 1);
} catch (final Throwable throwable) {
LOGGER.warn("Could not initialize security provider", throwable);
}
ConnectionPool.getInstance(this).reconfigure();
DynamicColors.applyToActivitiesIfAvailable(this);
}

View file

@ -0,0 +1,193 @@
package im.conversations.android.tls;
import android.os.Build;
import androidx.annotation.RequiresApi;
import com.google.common.base.Strings;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import org.conscrypt.Conscrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SSLSockets {
private static final String[] ENABLED_CIPHERS = {
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_CAMELLIA_256_SHA",
// Fallback.
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_128_GCM_SHA384",
"TLS_RSA_WITH_AES_256_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA384",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
};
private static final String[] WEAK_CIPHER_PATTERNS = {
"_NULL_", "_EXPORT_", "_anon_", "_RC4_", "_DES_", "_MD5",
};
private static final Logger LOGGER = LoggerFactory.getLogger(SSLSockets.class);
public static void setSecurity(final SSLSocket sslSocket) {
final String[] supportProtocols;
final Collection<String> supportedProtocols =
new LinkedList<>(Arrays.asList(sslSocket.getSupportedProtocols()));
supportedProtocols.remove("SSLv3");
supportProtocols = supportedProtocols.toArray(new String[0]);
sslSocket.setEnabledProtocols(supportProtocols);
final String[] cipherSuites = getOrderedCipherSuites(sslSocket.getSupportedCipherSuites());
if (cipherSuites.length > 0) {
sslSocket.setEnabledCipherSuites(cipherSuites);
}
}
public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) {
final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(ENABLED_CIPHERS));
final List<String> platformCiphers = Arrays.asList(platformSupportedCipherSuites);
cipherSuites.retainAll(platformCiphers);
cipherSuites.addAll(platformCiphers);
filterWeakCipherSuites(cipherSuites);
cipherSuites.remove("TLS_FALLBACK_SCSV");
return cipherSuites.toArray(new String[0]);
}
private static void filterWeakCipherSuites(final Collection<String> cipherSuites) {
final Iterator<String> it = cipherSuites.iterator();
while (it.hasNext()) {
String cipherName = it.next();
// remove all ciphers with no or very weak encryption or no authentication
for (final String weakCipherPattern : WEAK_CIPHER_PATTERNS) {
if (cipherName.contains(weakCipherPattern)) {
it.remove();
break;
}
}
}
}
public static void setHostname(final SSLSocket socket, final String hostname) {
if (Conscrypt.isConscrypt(socket)) {
Conscrypt.setHostname(socket, hostname);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setHostnameNougat(socket, hostname);
} else {
setHostnameReflection(socket, hostname);
}
}
private static void setHostnameReflection(final SSLSocket socket, final String hostname) {
try {
socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
} catch (final IllegalAccessException
| NoSuchMethodException
| InvocationTargetException e) {
LOGGER.warn("Could not set SNI hostname on socket", e);
}
}
@RequiresApi(api = Build.VERSION_CODES.N)
private static void setHostnameNougat(final SSLSocket socket, final String hostname) {
final SSLParameters parameters = new SSLParameters();
parameters.setServerNames(Collections.singletonList(new SNIHostName(hostname)));
socket.setSSLParameters(parameters);
}
private static void setApplicationProtocolReflection(
final SSLSocket socket, final String protocol) {
try {
final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class);
// the concatenation of 8-bit, length prefixed protocol names, just one in our case...
// http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
final byte[] protocolUTF8Bytes = protocol.getBytes(StandardCharsets.UTF_8);
final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1];
lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow
System.arraycopy(
protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length);
method.invoke(socket, new Object[] {lengthPrefixedProtocols});
} catch (final IllegalAccessException
| InvocationTargetException
| NoSuchMethodException e) {
LOGGER.warn("Could not set application protocol on socket", e);
}
}
public static void setApplicationProtocol(final SSLSocket socket, final String protocol) {
if (Conscrypt.isConscrypt(socket)) {
Conscrypt.setApplicationProtocols(socket, new String[] {protocol});
} else {
setApplicationProtocolReflection(socket, protocol);
}
}
public static SSLContext getSSLContext() throws NoSuchAlgorithmException {
try {
return SSLContext.getInstance("TLSv1.3");
} catch (NoSuchAlgorithmException e) {
return SSLContext.getInstance("TLSv1.2");
}
}
public static Version version(final Socket socket) {
if (socket instanceof SSLSocket) {
final SSLSocket sslSocket = (SSLSocket) socket;
return Version.of(sslSocket.getSession().getProtocol());
} else {
return Version.NONE;
}
}
public enum Version {
TLS_1_0,
TLS_1_1,
TLS_1_2,
TLS_1_3,
UNKNOWN,
NONE;
private static Version of(final String protocol) {
switch (Strings.nullToEmpty(protocol)) {
case "TLSv1":
return TLS_1_0;
case "TLSv1.1":
return TLS_1_1;
case "TLSv1.2":
return TLS_1_2;
case "TLSv1.3":
return TLS_1_3;
default:
return UNKNOWN;
}
}
}
}

View file

@ -6,11 +6,9 @@ import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import im.conversations.android.database.model.Account;
import im.conversations.android.repository.AccountRepository;
import im.conversations.android.ui.Event;
@ -21,8 +19,6 @@ import org.jxmpp.stringprep.XmppStringprepException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Future;
public class SetupViewModel extends AndroidViewModel {
private static final Logger LOGGER = LoggerFactory.getLogger(SetupViewModel.class);
@ -70,24 +66,28 @@ public class SetupViewModel extends AndroidViewModel {
public boolean submitPassword() {
final BareJid address;
try {
address =JidCreate.bareFrom(this.xmppAddress.getValue());
address = JidCreate.bareFrom(this.xmppAddress.getValue());
} catch (final XmppStringprepException e) {
xmppAddressError.postValue("Not a valid jid");
return true;
}
final String password = this.password.getValue();
final var creationFuture = this.accountRepository.createAccountAsync(address,password, true);
Futures.addCallback(creationFuture, new FutureCallback<Account>() {
@Override
public void onSuccess(final Account account) {
LOGGER.info("Successfully created account {}",account.address);
}
final var creationFuture =
this.accountRepository.createAccountAsync(address, password, true);
Futures.addCallback(
creationFuture,
new FutureCallback<Account>() {
@Override
public void onSuccess(final Account account) {
LOGGER.info("Successfully created account {}", account.address);
}
@Override
public void onFailure(@NonNull final Throwable t) {
LOGGER.warn("Could not create account", t);
}
}, MoreExecutors.directExecutor());
@Override
public void onFailure(@NonNull final Throwable t) {
LOGGER.warn("Could not create account", t);
}
},
MoreExecutors.directExecutor());
return true;
}

View file

@ -28,7 +28,6 @@ import eu.siacs.conversations.ui.util.PendingItem;
import eu.siacs.conversations.utils.Patterns;
import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.utils.Resolver;
import eu.siacs.conversations.utils.SSLSockets;
import eu.siacs.conversations.utils.SocksSocketFactory;
import eu.siacs.conversations.xmpp.bind.Bind2;
import im.conversations.android.Conversations;
@ -38,6 +37,7 @@ import im.conversations.android.database.CredentialStore;
import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.Connection;
import im.conversations.android.database.model.Credential;
import im.conversations.android.tls.SSLSockets;
import im.conversations.android.xml.Element;
import im.conversations.android.xml.Namespace;
import im.conversations.android.xml.Tag;

View file

@ -9,7 +9,7 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableBiMap;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.SSLSockets;
import im.conversations.android.tls.SSLSockets;
import im.conversations.android.xml.Element;
import im.conversations.android.xml.Namespace;
import java.util.Arrays;

View file

@ -1,13 +1,18 @@
package im.conversations.android.xmpp.sasl;
import android.util.Base64;
import com.google.common.base.Strings;
import eu.siacs.conversations.utils.CryptoHelper;
import com.google.common.io.BaseEncoding;
import im.conversations.android.IDs;
import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.Credential;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLSocket;
public class DigestMd5 extends SaslMechanism {
@ -58,21 +63,21 @@ public class DigestMd5 extends SaslMechanism {
+ Strings.nullToEmpty(credential.password);
final MessageDigest md = MessageDigest.getInstance("MD5");
final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
final String cNonce = CryptoHelper.random(100);
final String cNonce = IDs.huge();
final byte[] a1 =
concatenate(
y,
(":" + nonce + ":" + cNonce)
.getBytes(Charset.defaultCharset()));
final String a2 = "AUTHENTICATE:" + digestUri;
final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
final String ha1 = bytesToHex(md.digest(a1));
final String ha2 =
CryptoHelper.bytesToHex(
bytesToHex(
md.digest(a2.getBytes(Charset.defaultCharset())));
final String kd =
ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce + ":auth:" + ha2;
final String response =
CryptoHelper.bytesToHex(
bytesToHex(
md.digest(kd.getBytes(Charset.defaultCharset())));
final String saslString =
"username=\""
@ -110,4 +115,8 @@ public class DigestMd5 extends SaslMechanism {
}
return null;
}
private static String bytesToHex(final byte[] bytes) {
return BaseEncoding.base16().lowerCase().encode(bytes);
}
}

View file

@ -9,9 +9,9 @@ import com.google.common.collect.Multimap;
import com.google.common.hash.HashFunction;
import com.google.common.primitives.Bytes;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.SSLSockets;
import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.Credential;
import im.conversations.android.tls.SSLSockets;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;

View file

@ -5,9 +5,9 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.SSLSockets;
import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.Credential;
import im.conversations.android.tls.SSLSockets;
import im.conversations.android.xml.Element;
import im.conversations.android.xml.Namespace;
import java.util.Collection;