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}