move SSLSockets helper library into im.conversations package
This commit is contained in:
parent
5866974eff
commit
7d34c894d0
|
@ -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);
|
||||
}
|
||||
|
|
193
src/main/java/im/conversations/android/tls/SSLSockets.java
Normal file
193
src/main/java/im/conversations/android/tls/SSLSockets.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>() {
|
||||
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);
|
||||
LOGGER.info("Successfully created account {}", account.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull final Throwable t) {
|
||||
LOGGER.warn("Could not create account", t);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue