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 org.conscrypt.Conscrypt;
11
12import java.lang.reflect.Method;
13import java.net.Socket;
14import java.nio.charset.StandardCharsets;
15import java.security.NoSuchAlgorithmException;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.LinkedList;
20
21import javax.net.ssl.SNIHostName;
22import javax.net.ssl.SSLContext;
23import javax.net.ssl.SSLParameters;
24import javax.net.ssl.SSLSession;
25import javax.net.ssl.SSLSocket;
26
27import eu.siacs.conversations.Config;
28import eu.siacs.conversations.entities.Account;
29
30public class SSLSockets {
31
32 public static void setSecurity(final SSLSocket sslSocket) {
33 final String[] supportProtocols;
34 final Collection<String> supportedProtocols = new LinkedList<>(
35 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 = CryptoHelper.getOrderedCipherSuites(
42 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(final SSLSocket socket, final String protocol) {
74 try {
75 final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class);
76 // the concatenation of 8-bit, length prefixed protocol names, just one in our case...
77 // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
78 final byte[] protocolUTF8Bytes = protocol.getBytes(StandardCharsets.UTF_8);
79 final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1];
80 lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow
81 System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length);
82 method.invoke(socket, new Object[]{lengthPrefixedProtocols});
83 } catch (Throwable e) {
84 Log.e(Config.LOGTAG,"unable to set ALPN on socket",e);
85 }
86 }
87
88 public static void setApplicationProtocol(final SSLSocket socket, final String protocol) {
89 if (Conscrypt.isConscrypt(socket)) {
90 Conscrypt.setApplicationProtocols(socket, new String[]{protocol});
91 } else {
92 setApplicationProtocolReflection(socket, protocol);
93 }
94 }
95
96 public static SSLContext getSSLContext() throws NoSuchAlgorithmException {
97 try {
98 return SSLContext.getInstance("TLSv1.3");
99 } catch (NoSuchAlgorithmException e) {
100 return SSLContext.getInstance("TLSv1.2");
101 }
102 }
103
104 public static void log(Account account, SSLSocket socket) {
105 SSLSession session = socket.getSession();
106 Log.d(
107 Config.LOGTAG,
108 account.getJid().asBareJid()
109 + ": protocol="
110 + session.getProtocol()
111 + " cipher="
112 + session.getCipherSuite());
113 }
114
115 public static Version version(final Socket socket) {
116 if (socket instanceof SSLSocket) {
117 final SSLSocket sslSocket = (SSLSocket) socket;
118 return Version.of(sslSocket.getSession().getProtocol());
119 } else {
120 return Version.NONE;
121 }
122 }
123
124 public enum Version {
125 TLS_1_0,
126 TLS_1_1,
127 TLS_1_2,
128 TLS_1_3,
129 UNKNOWN,
130 NONE;
131
132 private static Version of(final String protocol) {
133 switch (Strings.nullToEmpty(protocol)) {
134 case "TLSv1":
135 return TLS_1_0;
136 case "TLSv1.1":
137 return TLS_1_1;
138 case "TLSv1.2":
139 return TLS_1_2;
140 case "TLSv1.3":
141 return TLS_1_3;
142 default:
143 return UNKNOWN;
144 }
145 }
146 }
147}