1 /*
2  * Copyright (C) 2012 Square, Inc.
3  * Copyright (C) 2012 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package okhttp3.internal.platform;
18
19 import java.io.IOException;
20 import java.lang.reflect.Field;
21 import java.net.InetSocketAddress;
22 import java.net.Socket;
23 import java.security.NoSuchAlgorithmException;
24 import java.security.Security;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29 import javax.annotation.Nullable;
30 import javax.net.ssl.SSLContext;
31 import javax.net.ssl.SSLSocket;
32 import javax.net.ssl.SSLSocketFactory;
33 import javax.net.ssl.X509TrustManager;
34 import okhttp3.OkHttpClient;
35 import okhttp3.Protocol;
36 import okhttp3.internal.Util;
37 import okhttp3.internal.tls.BasicCertificateChainCleaner;
38 import okhttp3.internal.tls.BasicTrustRootIndex;
39 import okhttp3.internal.tls.CertificateChainCleaner;
40 import okhttp3.internal.tls.TrustRootIndex;
41 import okio.Buffer;
42
43 /**
44  * Access to platform-specific features.
45  *
46  * <h3>Server name indication (SNI)</h3>
47  *
48  * <p>Supported on Android 2.3+.
49  *
50  * <p>Supported on OpenJDK 7+
51  *
52  * <h3>Session Tickets</h3>
53  *
54  * <p>Supported on Android 2.3+.
55  *
56  * <h3>Android Traffic Stats (Socket Tagging)</h3>
57  *
58  * <p>Supported on Android 4.0+.
59  *
60  * <h3>ALPN (Application Layer Protocol Negotiation)</h3>
61  *
62  * <p>Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was
63  * unstable.
64  *
65  * <p>Supported on OpenJDK 8 via the JettyALPN-boot library.
66  *
67  * <p>Supported on OpenJDK 9+ via SSLParameters and SSLSocket features.
68  *
69  * <h3>Trust Manager Extraction</h3>
70  *
71  * <p>Supported on Android 2.3+ and OpenJDK 7+. There are no public APIs to recover the trust
72  * manager that was used to create an {@link SSLSocketFactory}.
73  *
74  * <h3>Android Cleartext Permit Detection</h3>
75  *
76  * <p>Supported on Android 6.0+ via {@code NetworkSecurityPolicy}.
77  */

78 public class Platform {
79   private static final Platform PLATFORM = findPlatform();
80   public static final int INFO = 4;
81   public static final int WARN = 5;
82   private static final Logger logger = Logger.getLogger(OkHttpClient.class.getName());
83
84   public static Platform get() {
85     return PLATFORM;
86   }
87
88   /** Prefix used on custom headers. */
89   public String getPrefix() {
90     return "OkHttp";
91   }
92
93   protected @Nullable X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
94     // Attempt to get the trust manager from an OpenJDK socket factory. We attempt this on all
95     // platforms in order to support Robolectric, which mixes classes from both Android and the
96     // Oracle JDK. Note that we don't support HTTP/2 or other nice features on Robolectric.
97     try {
98       Class<?> sslContextClass = Class.forName("sun.security.ssl.SSLContextImpl");
99       Object context = readFieldOrNull(sslSocketFactory, sslContextClass, "context");
100       if (context == nullreturn null;
101       return readFieldOrNull(context, X509TrustManager.class"trustManager");
102     } catch (ClassNotFoundException e) {
103       return null;
104     }
105   }
106
107   /**
108    * Configure TLS extensions on {@code sslSocket} for {@code route}.
109    *
110    * @param hostname non-null for client-side handshakes; null for server-side handshakes.
111    */

112   public void configureTlsExtensions(SSLSocket sslSocket, @Nullable String hostname,
113       List<Protocol> protocols) throws IOException {
114   }
115
116   /**
117    * Called after the TLS handshake to release resources allocated by {@link
118    * #configureTlsExtensions}.
119    */

120   public void afterHandshake(SSLSocket sslSocket) {
121   }
122
123   /** Returns the negotiated protocol, or null if no protocol was negotiated. */
124   public @Nullable String getSelectedProtocol(SSLSocket socket) {
125     return null;
126   }
127
128   public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout)
129       throws IOException {
130     socket.connect(address, connectTimeout);
131   }
132
133   public void log(int level, String message, @Nullable Throwable t) {
134     Level logLevel = level == WARN ? Level.WARNING : Level.INFO;
135     logger.log(logLevel, message, t);
136   }
137
138   public boolean isCleartextTrafficPermitted(String hostname) {
139     return true;
140   }
141
142   /**
143    * Returns an object that holds a stack trace created at the moment this method is executed. This
144    * should be used specifically for {@link java.io.Closeable} objects and in conjunction with
145    * {@link #logCloseableLeak(String, Object)}.
146    */

147   public @Nullable Object getStackTraceForCloseable(String closer) {
148     if (logger.isLoggable(Level.FINE)) {
149       return new Throwable(closer); // These are expensive to allocate.
150     }
151     return null;
152   }
153
154   public void logCloseableLeak(String message, Object stackTrace) {
155     if (stackTrace == null) {
156       message += " To see where this was allocated, set the OkHttpClient logger level to FINE: "
157           + "Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE);";
158     }
159     log(WARN, message, (Throwable) stackTrace);
160   }
161
162   public static List<String> alpnProtocolNames(List<Protocol> protocols) {
163     List<String> names = new ArrayList<>(protocols.size());
164     for (int i = 0, size = protocols.size(); i < size; i++) {
165       Protocol protocol = protocols.get(i);
166       if (protocol == Protocol.HTTP_1_0) continue// No HTTP/1.0 for ALPN.
167       names.add(protocol.toString());
168     }
169     return names;
170   }
171
172   public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) {
173     return new BasicCertificateChainCleaner(buildTrustRootIndex(trustManager));
174   }
175
176   public CertificateChainCleaner buildCertificateChainCleaner(SSLSocketFactory sslSocketFactory) {
177     X509TrustManager trustManager = trustManager(sslSocketFactory);
178
179     if (trustManager == null) {
180       throw new IllegalStateException("Unable to extract the trust manager on "
181           + Platform.get()
182           + ", sslSocketFactory is "
183           + sslSocketFactory.getClass());
184     }
185
186     return buildCertificateChainCleaner(trustManager);
187   }
188
189   public static boolean isConscryptPreferred() {
190     // mainly to allow tests to run cleanly
191     if ("conscrypt".equals(Util.getSystemProperty("okhttp.platform"null))) {
192       return true;
193     }
194
195     // check if Provider manually installed
196     String preferredProvider = Security.getProviders()[0].getName();
197     return "Conscrypt".equals(preferredProvider);
198   }
199
200   /** Attempt to match the host runtime to a capable Platform implementation. */
201   private static Platform findPlatform() {
202     if (isAndroid()) {
203       return findAndroidPlatform();
204     } else {
205       return findJvmPlatform();
206     }
207   }
208
209   public static boolean isAndroid() {
210     // This explicit check avoids activating in Android Studio with Android specific classes
211     // available when running plugins inside the IDE.
212     return "Dalvik".equals(System.getProperty("java.vm.name"));
213   }
214
215   private static Platform findJvmPlatform() {
216     if (isConscryptPreferred()) {
217       Platform conscrypt = ConscryptPlatform.buildIfSupported();
218
219       if (conscrypt != null) {
220         return conscrypt;
221       }
222     }
223
224     Platform jdk9 = Jdk9Platform.buildIfSupported();
225
226     if (jdk9 != null) {
227       return jdk9;
228     }
229
230     Platform jdkWithJettyBoot = Jdk8WithJettyBootPlatform.buildIfSupported();
231
232     if (jdkWithJettyBoot != null) {
233       return jdkWithJettyBoot;
234     }
235
236     // Probably an Oracle JDK like OpenJDK.
237     return new Platform();
238   }
239
240   private static Platform findAndroidPlatform() {
241     Platform android10 = Android10Platform.buildIfSupported();
242
243     if (android10 != null) {
244       return android10;
245     }
246
247     Platform android = AndroidPlatform.buildIfSupported();
248
249     if (android == null) {
250       throw new NullPointerException("No platform found on Android");
251     }
252
253     return android;
254   }
255
256   /**
257    * Returns the concatenation of 8-bit, length prefixed protocol names.
258    * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
259    */

260   static byte[] concatLengthPrefixed(List<Protocol> protocols) {
261     Buffer result = new Buffer();
262     for (int i = 0, size = protocols.size(); i < size; i++) {
263       Protocol protocol = protocols.get(i);
264       if (protocol == Protocol.HTTP_1_0) continue// No HTTP/1.0 for ALPN.
265       result.writeByte(protocol.toString().length());
266       result.writeUtf8(protocol.toString());
267     }
268     return result.readByteArray();
269   }
270
271   static @Nullable <T> T readFieldOrNull(Object instance, Class<T> fieldType, String fieldName) {
272     for (Class<?> c = instance.getClass(); c != Object.class; c = c.getSuperclass()) {
273       try {
274         Field field = c.getDeclaredField(fieldName);
275         field.setAccessible(true);
276         Object value = field.get(instance);
277         if (!fieldType.isInstance(value)) return null;
278         return fieldType.cast(value);
279       } catch (NoSuchFieldException ignored) {
280       } catch (IllegalAccessException e) {
281         throw new AssertionError();
282       }
283     }
284
285     // Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate.
286     if (!fieldName.equals("delegate")) {
287       Object delegate = readFieldOrNull(instance, Object.class"delegate");
288       if (delegate != nullreturn readFieldOrNull(delegate, fieldType, fieldName);
289     }
290
291     return null;
292   }
293
294   public SSLContext getSSLContext() {
295     try {
296       return SSLContext.getInstance("TLS");
297     } catch (NoSuchAlgorithmException e) {
298       throw new IllegalStateException("No TLS provider", e);
299     }
300   }
301
302   public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) {
303     return new BasicTrustRootIndex(trustManager.getAcceptedIssuers());
304   }
305
306   public void configureSslSocketFactory(SSLSocketFactory socketFactory) {
307   }
308
309   @Override public String toString() {
310     return getClass().getSimpleName();
311   }
312 }
313