SSLSocketHelper.java

  1package eu.siacs.conversations.utils;
  2
  3import android.os.Build;
  4import android.util.Log;
  5
  6import androidx.annotation.RequiresApi;
  7
  8import org.conscrypt.Conscrypt;
  9
 10import java.lang.reflect.Method;
 11import java.nio.charset.StandardCharsets;
 12import java.security.NoSuchAlgorithmException;
 13import java.util.Arrays;
 14import java.util.Collection;
 15import java.util.Collections;
 16import java.util.LinkedList;
 17
 18import javax.net.ssl.SNIHostName;
 19import javax.net.ssl.SSLContext;
 20import javax.net.ssl.SSLParameters;
 21import javax.net.ssl.SSLSession;
 22import javax.net.ssl.SSLSocket;
 23
 24import eu.siacs.conversations.Config;
 25import eu.siacs.conversations.entities.Account;
 26
 27public class SSLSocketHelper {
 28
 29    public static void setSecurity(final SSLSocket sslSocket) {
 30        final String[] supportProtocols;
 31        final Collection<String> supportedProtocols = new LinkedList<>(
 32                Arrays.asList(sslSocket.getSupportedProtocols()));
 33        supportedProtocols.remove("SSLv3");
 34        supportProtocols = supportedProtocols.toArray(new String[0]);
 35
 36        sslSocket.setEnabledProtocols(supportProtocols);
 37
 38        final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
 39                sslSocket.getSupportedCipherSuites());
 40        if (cipherSuites.length > 0) {
 41            sslSocket.setEnabledCipherSuites(cipherSuites);
 42        }
 43    }
 44
 45    public static void setHostname(final SSLSocket socket, final String hostname) {
 46        if (Conscrypt.isConscrypt(socket)) {
 47            Conscrypt.setHostname(socket, hostname);
 48        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 49            setHostnameNougat(socket, hostname);
 50        } else {
 51            setHostnameReflection(socket, hostname);
 52        }
 53    }
 54
 55    private static void setHostnameReflection(final SSLSocket socket, final String hostname) {
 56        try {
 57            socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
 58        } catch (Throwable e) {
 59            Log.e(Config.LOGTAG, "unable to set SNI name on socket (" + hostname + ")", e);
 60        }
 61    }
 62
 63    @RequiresApi(api = Build.VERSION_CODES.N)
 64    private static void setHostnameNougat(final SSLSocket socket, final String hostname) {
 65        final SSLParameters parameters = new SSLParameters();
 66        parameters.setServerNames(Collections.singletonList(new SNIHostName(hostname)));
 67        socket.setSSLParameters(parameters);
 68    }
 69
 70    private static void setApplicationProtocolReflection(final SSLSocket socket, final String protocol) {
 71        try {
 72            final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class);
 73            // the concatenation of 8-bit, length prefixed protocol names, just one in our case...
 74            // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
 75            final byte[] protocolUTF8Bytes = protocol.getBytes(StandardCharsets.UTF_8);
 76            final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1];
 77            lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow
 78            System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length);
 79            method.invoke(socket, new Object[]{lengthPrefixedProtocols});
 80        } catch (Throwable e) {
 81            Log.e(Config.LOGTAG,"unable to set ALPN on socket",e);
 82        }
 83    }
 84
 85    public static void setApplicationProtocol(final SSLSocket socket, final String protocol) {
 86        if (Conscrypt.isConscrypt(socket)) {
 87            Conscrypt.setApplicationProtocols(socket, new String[]{protocol});
 88        } else {
 89            setApplicationProtocolReflection(socket, protocol);
 90        }
 91    }
 92
 93    public static SSLContext getSSLContext() throws NoSuchAlgorithmException {
 94        try {
 95            return SSLContext.getInstance("TLSv1.3");
 96        } catch (NoSuchAlgorithmException e) {
 97            return SSLContext.getInstance("TLSv1.2");
 98        }
 99    }
100
101    public static void log(Account account, SSLSocket socket) {
102        SSLSession session = socket.getSession();
103        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": protocol=" + session.getProtocol() + " cipher=" + session.getCipherSuite());
104    }
105}