okhttp3.internal.platform.AndroidPlatform Maven / Gradle / Ivy
/*
* Copyright (C) 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.internal.platform;
import android.os.Build;
import android.util.Log;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.Protocol;
import okhttp3.internal.Util;
import okhttp3.internal.tls.BasicTrustRootIndex;
import okhttp3.internal.tls.CertificateChainCleaner;
import okhttp3.internal.tls.TrustRootIndex;
import static okhttp3.internal.Util.assertionError;
/** Android 2.3 or better. */
class AndroidPlatform extends Platform {
private static final int MAX_LOG_LENGTH = 4000;
private final Class> sslParametersClass;
private final OptionalMethod setUseSessionTickets;
private final OptionalMethod setHostname;
// Non-null on Android 5.0+.
private final OptionalMethod getAlpnSelectedProtocol;
private final OptionalMethod setAlpnProtocols;
private final CloseGuard closeGuard = CloseGuard.get();
AndroidPlatform(Class> sslParametersClass, OptionalMethod setUseSessionTickets,
OptionalMethod setHostname, OptionalMethod getAlpnSelectedProtocol,
OptionalMethod setAlpnProtocols) {
this.sslParametersClass = sslParametersClass;
this.setUseSessionTickets = setUseSessionTickets;
this.setHostname = setHostname;
this.getAlpnSelectedProtocol = getAlpnSelectedProtocol;
this.setAlpnProtocols = setAlpnProtocols;
}
@Override public void connectSocket(Socket socket, InetSocketAddress address,
int connectTimeout) throws IOException {
try {
socket.connect(address, connectTimeout);
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} catch (SecurityException e) {
// Before android 4.3, socket.connect could throw a SecurityException
// if opening a socket resulted in an EACCES error.
IOException ioException = new IOException("Exception in connect");
ioException.initCause(e);
throw ioException;
} catch (ClassCastException e) {
// On android 8.0, socket.connect throws a ClassCastException due to a bug
// see https://issuetracker.google.com/issues/63649622
if (Build.VERSION.SDK_INT == 26) {
IOException ioException = new IOException("Exception in connect");
ioException.initCause(e);
throw ioException;
} else {
throw e;
}
}
}
@Override protected @Nullable X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters");
if (context == null) {
// If that didn't work, try the Google Play Services SSL provider before giving up. This
// must be loaded by the SSLSocketFactory's class loader.
try {
Class> gmsSslParametersClass = Class.forName(
"com.google.android.gms.org.conscrypt.SSLParametersImpl", false,
sslSocketFactory.getClass().getClassLoader());
context = readFieldOrNull(sslSocketFactory, gmsSslParametersClass, "sslParameters");
} catch (ClassNotFoundException e) {
return super.trustManager(sslSocketFactory);
}
}
X509TrustManager x509TrustManager = readFieldOrNull(
context, X509TrustManager.class, "x509TrustManager");
if (x509TrustManager != null) return x509TrustManager;
return readFieldOrNull(context, X509TrustManager.class, "trustManager");
}
@Override public void configureTlsExtensions(
SSLSocket sslSocket, String hostname, List protocols) throws IOException {
// Enable SNI and session tickets.
if (hostname != null) {
setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true);
setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname);
}
// Enable ALPN.
if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) {
Object[] parameters = {concatLengthPrefixed(protocols)};
setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters);
}
}
@Override public @Nullable String getSelectedProtocol(SSLSocket socket) {
if (getAlpnSelectedProtocol == null) return null;
if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
}
@Override public void log(int level, String message, @Nullable Throwable t) {
int logLevel = level == WARN ? Log.WARN : Log.DEBUG;
if (t != null) message = message + '\n' + Log.getStackTraceString(t);
// Split by line, then ensure each line can fit into Log's maximum length.
for (int i = 0, length = message.length(); i < length; i++) {
int newline = message.indexOf('\n', i);
newline = newline != -1 ? newline : length;
do {
int end = Math.min(newline, i + MAX_LOG_LENGTH);
Log.println(logLevel, "OkHttp", message.substring(i, end));
i = end;
} while (i < newline);
}
}
@Override public Object getStackTraceForCloseable(String closer) {
return closeGuard.createAndOpen(closer);
}
@Override public void logCloseableLeak(String message, Object stackTrace) {
boolean reported = closeGuard.warnIfOpen(stackTrace);
if (!reported) {
// Unable to report via CloseGuard. As a last-ditch effort, send it to the logger.
log(WARN, message, null);
}
}
@Override public boolean isCleartextTrafficPermitted(String hostname) {
// Don't look for NetworkSecurityPolicy on older devices; that might crash them.
// https://github.com/square/okhttp/issues/3772#issuecomment-527711999
if (Build.VERSION.SDK_INT < 23) {
return super.isCleartextTrafficPermitted(hostname);
}
try {
Class> networkPolicyClass = Class.forName("android.security.NetworkSecurityPolicy");
Method getInstanceMethod = networkPolicyClass.getMethod("getInstance");
Object networkSecurityPolicy = getInstanceMethod.invoke(null);
return api24IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy);
} catch (ClassNotFoundException | NoSuchMethodException e) {
return super.isCleartextTrafficPermitted(hostname);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw assertionError("unable to determine cleartext support", e);
}
}
private boolean api24IsCleartextTrafficPermitted(String hostname, Class> networkPolicyClass,
Object networkSecurityPolicy) throws InvocationTargetException, IllegalAccessException {
try {
Method isCleartextTrafficPermittedMethod = networkPolicyClass
.getMethod("isCleartextTrafficPermitted", String.class);
return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy, hostname);
} catch (NoSuchMethodException e) {
return api23IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy);
}
}
private boolean api23IsCleartextTrafficPermitted(String hostname, Class> networkPolicyClass,
Object networkSecurityPolicy) throws InvocationTargetException, IllegalAccessException {
try {
Method isCleartextTrafficPermittedMethod = networkPolicyClass
.getMethod("isCleartextTrafficPermitted");
return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy);
} catch (NoSuchMethodException e) {
return super.isCleartextTrafficPermitted(hostname);
}
}
/**
* Checks to see if Google Play Services Dynamic Security Provider is present which provides ALPN
* support. If it isn't checks to see if device is Android 5.0+ since 4.x device have broken
* ALPN support.
*/
private static boolean supportsAlpn() {
if (Security.getProvider("GMSCore_OpenSSL") != null) {
return true;
} else {
try {
Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.
return true;
} catch (ClassNotFoundException ignored) { }
}
return false;
}
public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) {
try {
Class> extensionsClass = Class.forName("android.net.http.X509TrustManagerExtensions");
Constructor> constructor = extensionsClass.getConstructor(X509TrustManager.class);
Object extensions = constructor.newInstance(trustManager);
Method checkServerTrusted = extensionsClass.getMethod(
"checkServerTrusted", X509Certificate[].class, String.class, String.class);
return new AndroidCertificateChainCleaner(extensions, checkServerTrusted);
} catch (Exception e) {
return super.buildCertificateChainCleaner(trustManager);
}
}
public static Platform buildIfSupported() {
if (!Platform.isAndroid()) {
return null;
}
// Attempt to find Android 2.3+ APIs.
try {
Class> sslParametersClass;
try {
sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");
} catch (ClassNotFoundException e) {
// Older platform before being unbundled.
sslParametersClass = Class.forName(
"org.apache.harmony.xnet.provider.jsse.SSLParametersImpl");
}
OptionalMethod setUseSessionTickets = new OptionalMethod<>(
null, "setUseSessionTickets", boolean.class);
OptionalMethod setHostname = new OptionalMethod<>(
null, "setHostname", String.class);
OptionalMethod getAlpnSelectedProtocol = null;
OptionalMethod setAlpnProtocols = null;
if (supportsAlpn()) {
getAlpnSelectedProtocol
= new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
setAlpnProtocols
= new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
}
return new AndroidPlatform(sslParametersClass, setUseSessionTickets, setHostname,
getAlpnSelectedProtocol, setAlpnProtocols);
} catch (ClassNotFoundException ignored) {
// This isn't an Android runtime.
}
return null;
}
@Override
public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) {
try {
// From org.conscrypt.TrustManagerImpl, we want the method with this signature:
// private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert);
Method method = trustManager.getClass().getDeclaredMethod(
"findTrustAnchorByIssuerAndSignature", X509Certificate.class);
method.setAccessible(true);
return new AndroidTrustRootIndex(trustManager, method);
} catch (NoSuchMethodException e) {
return super.buildTrustRootIndex(trustManager);
}
}
/**
* X509TrustManagerExtensions was added to Android in API 17 (Android 4.2, released in late 2012).
* This is the best way to get a clean chain on Android because it uses the same code as the TLS
* handshake.
*/
static final class AndroidCertificateChainCleaner extends CertificateChainCleaner {
private final Object x509TrustManagerExtensions;
private final Method checkServerTrusted;
AndroidCertificateChainCleaner(Object x509TrustManagerExtensions, Method checkServerTrusted) {
this.x509TrustManagerExtensions = x509TrustManagerExtensions;
this.checkServerTrusted = checkServerTrusted;
}
@SuppressWarnings({"unchecked", "SuspiciousToArrayCall"}) // Reflection on List.
@Override public List clean(List chain, String hostname)
throws SSLPeerUnverifiedException {
try {
X509Certificate[] certificates = chain.toArray(new X509Certificate[chain.size()]);
return (List) checkServerTrusted.invoke(
x509TrustManagerExtensions, certificates, "RSA", hostname);
} catch (InvocationTargetException e) {
SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
exception.initCause(e);
throw exception;
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
@Override public boolean equals(Object other) {
return other instanceof AndroidCertificateChainCleaner; // All instances are equivalent.
}
@Override public int hashCode() {
return 0;
}
}
/**
* Provides access to the internal dalvik.system.CloseGuard class. Android uses this in
* combination with android.os.StrictMode to report on leaked java.io.Closeable's. Available since
* Android API 11.
*/
static final class CloseGuard {
private final Method getMethod;
private final Method openMethod;
private final Method warnIfOpenMethod;
CloseGuard(Method getMethod, Method openMethod, Method warnIfOpenMethod) {
this.getMethod = getMethod;
this.openMethod = openMethod;
this.warnIfOpenMethod = warnIfOpenMethod;
}
Object createAndOpen(String closer) {
if (getMethod != null) {
try {
Object closeGuardInstance = getMethod.invoke(null);
openMethod.invoke(closeGuardInstance, closer);
return closeGuardInstance;
} catch (Exception ignored) {
}
}
return null;
}
boolean warnIfOpen(Object closeGuardInstance) {
boolean reported = false;
if (closeGuardInstance != null) {
try {
warnIfOpenMethod.invoke(closeGuardInstance);
reported = true;
} catch (Exception ignored) {
}
}
return reported;
}
static CloseGuard get() {
Method getMethod;
Method openMethod;
Method warnIfOpenMethod;
try {
Class> closeGuardClass = Class.forName("dalvik.system.CloseGuard");
getMethod = closeGuardClass.getMethod("get");
openMethod = closeGuardClass.getMethod("open", String.class);
warnIfOpenMethod = closeGuardClass.getMethod("warnIfOpen");
} catch (Exception ignored) {
getMethod = null;
openMethod = null;
warnIfOpenMethod = null;
}
return new CloseGuard(getMethod, openMethod, warnIfOpenMethod);
}
}
/**
* An index of trusted root certificates that exploits knowledge of Android implementation
* details. This class is potentially much faster to initialize than {@link BasicTrustRootIndex}
* because it doesn't need to load and index trusted CA certificates.
*
* This class uses APIs added to Android in API 14 (Android 4.0, released October 2011). This
* class shouldn't be used in Android API 17 or better because those releases are better served by
* {@link AndroidPlatform.AndroidCertificateChainCleaner}.
*/
static final class AndroidTrustRootIndex implements TrustRootIndex {
private final X509TrustManager trustManager;
private final Method findByIssuerAndSignatureMethod;
AndroidTrustRootIndex(X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) {
this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod;
this.trustManager = trustManager;
}
@Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) {
try {
TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke(
trustManager, cert);
return trustAnchor != null
? trustAnchor.getTrustedCert()
: null;
} catch (IllegalAccessException e) {
throw assertionError("unable to get issues and signature", e);
} catch (InvocationTargetException e) {
return null;
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof AndroidTrustRootIndex)) {
return false;
}
AndroidTrustRootIndex that = (AndroidTrustRootIndex) obj;
return trustManager.equals(that.trustManager)
&& findByIssuerAndSignatureMethod.equals(that.findByIssuerAndSignatureMethod);
}
@Override
public int hashCode() {
return trustManager.hashCode() + 31 * findByIssuerAndSignatureMethod.hashCode();
}
}
@Override public SSLContext getSSLContext() {
boolean tryTls12;
try {
tryTls12 = (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22);
} catch (NoClassDefFoundError e) {
// Not a real Android runtime; probably RoboVM or MoE
// Try to load TLS 1.2 explicitly.
tryTls12 = true;
}
if (tryTls12) {
try {
return SSLContext.getInstance("TLSv1.2");
} catch (NoSuchAlgorithmException e) {
// fallback to TLS
}
}
try {
return SSLContext.getInstance("TLS");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("No TLS provider", e);
}
}
static int getSdkInt() {
try {
return Build.VERSION.SDK_INT;
} catch (NoClassDefFoundError ignored) {
// fails fatally against robolectric classes
return 0;
}
}
}