Home | History | Annotate | Download | only in internal
      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