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 com.squareup.okhttp.internal; 18 19 import com.squareup.okhttp.Protocol; 20 import com.squareup.okhttp.internal.tls.RealTrustRootIndex; 21 import com.squareup.okhttp.internal.tls.TrustRootIndex; 22 23 import java.io.IOException; 24 import java.lang.reflect.Field; 25 import java.net.InetSocketAddress; 26 import java.net.Socket; 27 import java.net.SocketException; 28 import java.util.List; 29 import java.util.concurrent.atomic.AtomicReference; 30 import javax.net.ssl.SSLSocket; 31 import javax.net.ssl.SSLSocketFactory; 32 import javax.net.ssl.X509TrustManager; 33 34 import dalvik.system.SocketTagger; 35 import okio.Buffer; 36 37 /** 38 * Access to proprietary Android APIs. Avoids use of reflection where possible. 39 */ 40 // only tests should extend this class 41 public class Platform { 42 private static final AtomicReference<Platform> INSTANCE_HOLDER 43 = new AtomicReference<>(new Platform()); 44 45 // only for private use and in tests 46 protected Platform() { 47 } 48 49 public static Platform get() { 50 return INSTANCE_HOLDER.get(); 51 } 52 53 /** 54 * Atomically replaces the Platform instance returned by future 55 * invocations of {@link #get()}, for use in tests. 56 * Invocations of this method should typically be followed by 57 * a try/finally block to reset the previous value: 58 * 59 * <pre> 60 * Platform p = getAndSetForTest(...); 61 * try { 62 * ... 63 * } finally { 64 * getAndSetForTest(p); 65 * } 66 * </pre> 67 * 68 * @return the previous value of {@link #get()}. 69 */ 70 public static Platform getAndSetForTest(Platform platform) { 71 if (platform == null) { 72 throw new NullPointerException(); 73 } 74 return INSTANCE_HOLDER.getAndSet(platform); 75 } 76 77 /** setUseSessionTickets(boolean) */ 78 private static final OptionalMethod<Socket> SET_USE_SESSION_TICKETS = 79 new OptionalMethod<Socket>(null, "setUseSessionTickets", Boolean.TYPE); 80 /** setHostname(String) */ 81 private static final OptionalMethod<Socket> SET_HOSTNAME = 82 new OptionalMethod<Socket>(null, "setHostname", String.class); 83 /** byte[] getAlpnSelectedProtocol() */ 84 private static final OptionalMethod<Socket> GET_ALPN_SELECTED_PROTOCOL = 85 new OptionalMethod<Socket>(byte[].class, "getAlpnSelectedProtocol"); 86 /** setAlpnSelectedProtocol(byte[]) */ 87 private static final OptionalMethod<Socket> SET_ALPN_PROTOCOLS = 88 new OptionalMethod<Socket>(null, "setAlpnProtocols", byte[].class ); 89 90 public void logW(String warning) { 91 System.logW(warning); 92 } 93 94 public void tagSocket(Socket socket) throws SocketException { 95 SocketTagger.get().tag(socket); 96 } 97 98 public void untagSocket(Socket socket) throws SocketException { 99 SocketTagger.get().untag(socket); 100 } 101 102 public void configureTlsExtensions( 103 SSLSocket sslSocket, String hostname, List<Protocol> protocols) { 104 // Enable SNI and session tickets. 105 if (hostname != null) { 106 SET_USE_SESSION_TICKETS.invokeOptionalWithoutCheckedException(sslSocket, true); 107 SET_HOSTNAME.invokeOptionalWithoutCheckedException(sslSocket, hostname); 108 } 109 110 // Enable ALPN. 111 boolean alpnSupported = SET_ALPN_PROTOCOLS.isSupported(sslSocket); 112 if (!alpnSupported) { 113 return; 114 } 115 116 Object[] parameters = { concatLengthPrefixed(protocols) }; 117 if (alpnSupported) { 118 SET_ALPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters); 119 } 120 } 121 122 /** 123 * Called after the TLS handshake to release resources allocated by {@link 124 * #configureTlsExtensions}. 125 */ 126 public void afterHandshake(SSLSocket sslSocket) { 127 } 128 129 public String getSelectedProtocol(SSLSocket socket) { 130 boolean alpnSupported = GET_ALPN_SELECTED_PROTOCOL.isSupported(socket); 131 if (!alpnSupported) { 132 return null; 133 } 134 135 byte[] alpnResult = 136 (byte[]) GET_ALPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket); 137 if (alpnResult != null) { 138 return new String(alpnResult, Util.UTF_8); 139 } 140 return null; 141 } 142 143 public void connectSocket(Socket socket, InetSocketAddress address, 144 int connectTimeout) throws IOException { 145 socket.connect(address, connectTimeout); 146 } 147 148 /** Prefix used on custom headers. */ 149 public String getPrefix() { 150 return "X-Android"; 151 } 152 153 /** 154 * Stripped down/adapted from OkHttp's {@code Platform.Android.trustManager()}. 155 * OkHttp 2.7.5 uses this only for certificate pinning logic that we don't use 156 * on Android, so this method should never be called outside of OkHttp's tests. 157 * This method has been stripped down to the minimum for OkHttp's tests to pass. 158 */ 159 public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { 160 Class sslParametersClass; 161 try { 162 sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl"); 163 } catch (ClassNotFoundException e) { 164 throw new RuntimeException(e); 165 } 166 Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters"); 167 return readFieldOrNull(context, X509TrustManager.class, "x509TrustManager"); 168 } 169 170 /** 171 * Stripped down from OkHttp's implementation to the minimum to get OkHttp's tests 172 * to pass. OkHttp 2.7.5 uses this for certificate pinning logic which is unused 173 * in Android. This method should never be called outside of OkHttp's tests. 174 */ 175 public TrustRootIndex trustRootIndex(X509TrustManager trustManager) { 176 return new RealTrustRootIndex(trustManager.getAcceptedIssuers()); 177 } 178 179 // Helper method from OkHttp's Platform.java 180 private static <T> T readFieldOrNull(Object instance, Class<T> fieldType, String fieldName) { 181 for (Class<?> c = instance.getClass(); c != Object.class; c = c.getSuperclass()) { 182 try { 183 Field field = c.getDeclaredField(fieldName); 184 field.setAccessible(true); 185 Object value = field.get(instance); 186 if (value == null || !fieldType.isInstance(value)) return null; 187 return fieldType.cast(value); 188 } catch (NoSuchFieldException ignored) { 189 } catch (IllegalAccessException e) { 190 throw new AssertionError(); 191 } 192 } 193 194 // Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate. 195 if (!fieldName.equals("delegate")) { 196 Object delegate = readFieldOrNull(instance, Object.class, "delegate"); 197 if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName); 198 } 199 200 return null; 201 } 202 203 /** 204 * Returns the concatenation of 8-bit, length prefixed protocol names. 205 * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 206 */ 207 static byte[] concatLengthPrefixed(List<Protocol> protocols) { 208 Buffer result = new Buffer(); 209 for (int i = 0, size = protocols.size(); i < size; i++) { 210 Protocol protocol = protocols.get(i); 211 if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN. 212 result.writeByte(protocol.toString().length()); 213 result.writeUtf8(protocol.toString()); 214 } 215 return result.readByteArray(); 216 } 217 } 218