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