use conscrypt as security provider to provide tls 1.3 and modern cyphers on old androids

Daniel Gultsch created

Change summary

build.gradle                                                             |   1 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |   4 
src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java          | 103 
src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java         |   8 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java            |   4 
5 files changed, 61 insertions(+), 59 deletions(-)

Detailed changes

build.gradle 🔗

@@ -52,6 +52,7 @@ dependencies {
     implementation 'rocks.xmpp:xmpp-addr:0.8.0'
     implementation 'org.osmdroid:osmdroid-android:6.0.1'
     implementation 'org.hsluv:hsluv:0.2'
+    implementation 'org.conscrypt:conscrypt-android:1.3.0'
 }
 
 ext {

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -40,12 +40,14 @@ import android.util.Log;
 import android.util.LruCache;
 import android.util.Pair;
 
+import org.conscrypt.Conscrypt;
 import org.openintents.openpgp.IOpenPgpService2;
 import org.openintents.openpgp.util.OpenPgpApi;
 import org.openintents.openpgp.util.OpenPgpServiceConnection;
 
 import java.net.URL;
 import java.security.SecureRandom;
+import java.security.Security;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
@@ -955,7 +957,7 @@ public class XmppConnectionService extends Service {
     public void onCreate() {
         OmemoSetting.load(this);
         ExceptionHelper.init(getApplicationContext());
-        PRNGFixes.apply();
+        Security.insertProviderAt(Conscrypt.newProvider(), 1);
         Resolver.init(this);
         this.mRandom = new SecureRandom();
         updateMemorizingTrustmanager();

src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java 🔗

@@ -1,6 +1,6 @@
 package eu.siacs.conversations.utils;
 
-import android.os.Build;
+import android.util.Log;
 
 import java.lang.reflect.Method;
 import java.security.NoSuchAlgorithmException;
@@ -9,65 +9,64 @@ import java.util.Collection;
 import java.util.LinkedList;
 
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+
 public class SSLSocketHelper {
 
-	public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException {
-		final String[] supportProtocols;
-		final Collection<String> supportedProtocols = new LinkedList<>(
-				Arrays.asList(sslSocket.getSupportedProtocols()));
-		supportedProtocols.remove("SSLv3");
-		supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]);
+    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[supportedProtocols.size()]);
+
+        sslSocket.setEnabledProtocols(supportProtocols);
 
-		sslSocket.setEnabledProtocols(supportProtocols);
+        final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
+                sslSocket.getSupportedCipherSuites());
+        if (cipherSuites.length > 0) {
+            sslSocket.setEnabledCipherSuites(cipherSuites);
+        }
+    }
 
-		final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
-				sslSocket.getSupportedCipherSuites());
-		if (cipherSuites.length > 0) {
-			sslSocket.setEnabledCipherSuites(cipherSuites);
-		}
-	}
+    public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) {
+        if (factory instanceof android.net.SSLCertificateSocketFactory) {
+            ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname);
+        }
+    }
 
-	public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) {
-		if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
-			((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname);
-		} else {
-			try {
-				socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
-			} catch (Throwable e) {
-				// ignore any error, we just can't set the hostname...
-			}
-		}
-	}
+    public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) {
+        try {
+            if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
+                // can't call directly because of @hide?
+                //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")});
+                android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}});
+            } else {
+                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("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 (Throwable e) {
+            // ignore any error, we just can't set the alpn protocol...
+        }
+    }
 
-	public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) {
-		try {
-			if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
-				// can't call directly because of @hide?
-				//((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")});
-				android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}});
-			} else {
-				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("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 (Throwable e) {
-			// ignore any error, we just can't set the alpn protocol...
-		}
-	}
+    public static SSLContext getSSLContext() throws NoSuchAlgorithmException {
+        return SSLContext.getInstance("TLSv1.3");
+    }
 
-	public static SSLContext getSSLContext() throws NoSuchAlgorithmException {
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-			return SSLContext.getInstance("TLSv1.2");
-		} else {
-			return SSLContext.getInstance("TLS");
-		}
-	}
+    public static void log(Account account, SSLSocket socket) {
+        SSLSession session = socket.getSession();
+        Log.d(Config.LOGTAG,account.getJid().asBareJid()+": protocol="+session.getProtocol()+" cipher="+session.getCipherSuite());
+    }
 }

src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java 🔗

@@ -16,7 +16,7 @@ public class TLSSocketFactory extends SSLSocketFactory {
     private final SSLSocketFactory internalSSLSocketFactory;
 
     public TLSSocketFactory(X509TrustManager[] trustManager, SecureRandom random) throws KeyManagementException, NoSuchAlgorithmException {
-        SSLContext context = SSLContext.getInstance("TLS");
+        SSLContext context = SSLSocketHelper.getSSLContext();
         context.init(null, trustManager, random);
         this.internalSSLSocketFactory = context.getSocketFactory();
     }
@@ -58,11 +58,7 @@ public class TLSSocketFactory extends SSLSocketFactory {
 
     private static Socket enableTLSOnSocket(Socket socket) {
         if(socket != null && (socket instanceof SSLSocket)) {
-            try {
-                SSLSocketHelper.setSecurity((SSLSocket) socket);
-            } catch (NoSuchAlgorithmException e) {
-                //ignoring
-            }
+            SSLSocketHelper.setSecurity((SSLSocket) socket);
         }
         return socket;
     }

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -455,6 +455,9 @@ public class XmppConnection implements Runnable {
 		if (Thread.currentThread().isInterrupted()) {
 			throw new InterruptedException();
 		}
+		if (socket instanceof SSLSocket) {
+			SSLSocketHelper.log(account, (SSLSocket) socket);
+		}
 		return tag != null && tag.isStart("stream");
 	}
 
@@ -852,6 +855,7 @@ public class XmppConnection implements Runnable {
 			features.encryptionEnabled = true;
 			final Tag tag = tagReader.readTag();
 			if (tag != null && tag.isStart("stream")) {
+				SSLSocketHelper.log(account, sslSocket);
 				processStream();
 			} else {
 				throw new IOException("server didn't restart stream after STARTTLS");