blob: 870e4c4f7ac2a406b0eab8ae465c67e943e9ccd7 [file] [log] [blame]
/*
* Copyright 2014 The Android Open Source Project
*
* 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 org.conscrypt;
import static org.conscrypt.metrics.Source.SOURCE_GMS;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.SystemClock;
import android.util.Log;
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketImpl;
import java.security.AlgorithmParameters;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.StandardConstants;
import javax.net.ssl.X509TrustManager;
import org.conscrypt.ct.CTLogStore;
import org.conscrypt.ct.CTPolicy;
import org.conscrypt.metrics.CipherSuite;
import org.conscrypt.metrics.ConscryptStatsLog;
import org.conscrypt.metrics.Protocol;
/**
* Platform-specific methods for unbundled Android.
*/
final class Platform {
private static final String TAG = "Conscrypt";
private static Method m_getCurveName;
static {
try {
m_getCurveName = ECParameterSpec.class.getDeclaredMethod("getCurveName");
m_getCurveName.setAccessible(true);
} catch (Exception ignored) {
//Ignored
}
}
private Platform() {}
public static void setup() {}
/**
* Default name used in the {@link java.security.Security JCE system} by {@code OpenSSLProvider}
* if the default constructor is used.
*/
public static String getDefaultProviderName() {
return "Conscrypt";
}
static boolean provideTrustManagerByDefault() {
return false;
}
public static FileDescriptor getFileDescriptor(Socket s) {
try {
Field f_impl = Socket.class.getDeclaredField("impl");
f_impl.setAccessible(true);
Object socketImpl = f_impl.get(s);
Field f_fd = SocketImpl.class.getDeclaredField("fd");
f_fd.setAccessible(true);
return (FileDescriptor) f_fd.get(socketImpl);
} catch (Exception e) {
throw new RuntimeException("Can't get FileDescriptor from socket", e);
}
}
public static FileDescriptor getFileDescriptorFromSSLSocket(AbstractConscryptSocket socket) {
return getFileDescriptor(socket);
}
public static String getCurveName(ECParameterSpec spec) {
if (m_getCurveName == null) {
return null;
}
try {
return (String) m_getCurveName.invoke(spec);
} catch (Exception e) {
return null;
}
}
public static void setCurveName(ECParameterSpec spec, String curveName) {
try {
Method setCurveName = spec.getClass().getDeclaredMethod("setCurveName", String.class);
setCurveName.invoke(spec, curveName);
} catch (Exception ignored) {
//Ignored
}
}
/**
* Call Os.setsockoptTimeval via reflection.
*/
public static void setSocketWriteTimeout(Socket s, long timeoutMillis) throws SocketException {
try {
FileDescriptor fd = getFileDescriptor(s);
if (fd == null || !fd.valid()) {
// Mirror the behavior of platform sockets when calling methods with bad fds
throw new SocketException("Socket closed");
}
Class<?> c_structTimeval =
getClass("android.system.StructTimeval", "libcore.io.StructTimeval");
if (c_structTimeval == null) {
Log.w(TAG, "StructTimeval == null; not setting socket write timeout");
return;
}
Method m_fromMillis = c_structTimeval.getDeclaredMethod("fromMillis", long.class);
if (m_fromMillis == null) {
Log.w(TAG, "fromMillis == null; not setting socket write timeout");
return;
}
Object timeval = m_fromMillis.invoke(null, timeoutMillis);
Class<?> c_Libcore = Class.forName("libcore.io.Libcore");
if (c_Libcore == null) {
Log.w(TAG, "Libcore == null; not setting socket write timeout");
return;
}
Field f_os = c_Libcore.getField("os");
if (f_os == null) {
Log.w(TAG, "os == null; not setting socket write timeout");
return;
}
Object instance_os = f_os.get(null);
if (instance_os == null) {
Log.w(TAG, "instance_os == null; not setting socket write timeout");
return;
}
Class<?> c_osConstants =
getClass("android.system.OsConstants", "libcore.io.OsConstants");
if (c_osConstants == null) {
Log.w(TAG, "OsConstants == null; not setting socket write timeout");
return;
}
Field f_SOL_SOCKET = c_osConstants.getField("SOL_SOCKET");
if (f_SOL_SOCKET == null) {
Log.w(TAG, "SOL_SOCKET == null; not setting socket write timeout");
return;
}
Field f_SO_SNDTIMEO = c_osConstants.getField("SO_SNDTIMEO");
if (f_SO_SNDTIMEO == null) {
Log.w(TAG, "SO_SNDTIMEO == null; not setting socket write timeout");
return;
}
Method m_setsockoptTimeval = instance_os.getClass().getMethod("setsockoptTimeval",
FileDescriptor.class, int.class, int.class, c_structTimeval);
if (m_setsockoptTimeval == null) {
Log.w(TAG, "setsockoptTimeval == null; not setting socket write timeout");
return;
}
m_setsockoptTimeval.invoke(instance_os, fd, f_SOL_SOCKET.get(null),
f_SO_SNDTIMEO.get(null), timeval);
} catch (Exception e) {
// We don't want to spam the logcat since this isn't a fatal error, but we want to know
// why this might be happening.
logStackTraceSnippet("Could not set socket write timeout: " + e, e);
Throwable cause = e.getCause();
while (cause != null) {
logStackTraceSnippet("Caused by: " + cause, cause);
cause = cause.getCause();
}
}
}
/**
* Logs an abbreviated stacktrace (summary and a couple of StackTraceElements).
*/
private static void logStackTraceSnippet(String summary, Throwable throwable) {
Log.w(TAG, summary);
StackTraceElement[] elements = throwable.getStackTrace();
for (int i = 0; i < 2 && i < elements.length; i++) {
Log.w(TAG, "\tat " + elements[i].toString());
}
}
private static void setSSLParametersOnImpl(SSLParameters params, SSLParametersImpl impl)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method m_getEndpointIdentificationAlgorithm =
params.getClass().getMethod("getEndpointIdentificationAlgorithm");
impl.setEndpointIdentificationAlgorithm(
(String) m_getEndpointIdentificationAlgorithm.invoke(params));
Method m_getUseCipherSuitesOrder = params.getClass().getMethod("getUseCipherSuitesOrder");
impl.setUseCipherSuitesOrder((boolean) m_getUseCipherSuitesOrder.invoke(params));
}
public static void setSSLParameters(
SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
try {
setSSLParametersOnImpl(params, impl);
if (Build.VERSION.SDK_INT >= 24) {
String sniHostname = getSniHostnameFromParams(params);
if (sniHostname != null) {
socket.setHostname(sniHostname);
}
}
} catch (NoSuchMethodException ignored) {
//Ignored
} catch (IllegalAccessException ignored) {
//Ignored
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
public static void setSSLParameters(
SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine) {
try {
setSSLParametersOnImpl(params, impl);
if (Build.VERSION.SDK_INT >= 24) {
String sniHostname = getSniHostnameFromParams(params);
if (sniHostname != null) {
engine.setHostname(sniHostname);
}
}
} catch (NoSuchMethodException ignored) {
//Ignored
} catch (IllegalAccessException ignored) {
//Ignored
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
@TargetApi(24)
private static String getSniHostnameFromParams(SSLParameters params)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method m_getServerNames = params.getClass().getMethod("getServerNames");
@SuppressWarnings("unchecked")
List<SNIServerName> serverNames = (List<SNIServerName>) m_getServerNames.invoke(params);
if (serverNames != null) {
for (SNIServerName serverName : serverNames) {
if (serverName.getType() == StandardConstants.SNI_HOST_NAME) {
return ((SNIHostName) serverName).getAsciiName();
}
}
}
return null;
}
private static void getSSLParametersFromImpl(SSLParameters params, SSLParametersImpl impl)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method m_setEndpointIdentificationAlgorithm =
params.getClass().getMethod("setEndpointIdentificationAlgorithm", String.class);
m_setEndpointIdentificationAlgorithm.invoke(
params, impl.getEndpointIdentificationAlgorithm());
Method m_setUseCipherSuitesOrder =
params.getClass().getMethod("setUseCipherSuitesOrder", boolean.class);
m_setUseCipherSuitesOrder.invoke(params, impl.getUseCipherSuitesOrder());
}
public static void getSSLParameters(
SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
try {
getSSLParametersFromImpl(params, impl);
if (Build.VERSION.SDK_INT >= 24) {
setParametersSniHostname(params, impl, socket);
}
} catch (NoSuchMethodException ignored) {
//Ignored
} catch (IllegalAccessException ignored) {
//Ignored
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
@TargetApi(24)
private static void setParametersSniHostname(
SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
if (impl.getUseSni() && AddressUtils.isValidSniHostname(socket.getHostname())) {
Method m_setServerNames = params.getClass().getMethod("setServerNames", List.class);
m_setServerNames.invoke(params,
Collections.<SNIServerName>singletonList(
new SNIHostName(socket.getHostname())));
}
}
public static void getSSLParameters(
SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine) {
try {
getSSLParametersFromImpl(params, impl);
if (Build.VERSION.SDK_INT >= 24) {
setParametersSniHostname(params, impl, engine);
}
} catch (NoSuchMethodException ignored) {
//Ignored
} catch (IllegalAccessException ignored) {
//Ignored
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
@TargetApi(24)
private static void setParametersSniHostname(
SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
if (impl.getUseSni() && AddressUtils.isValidSniHostname(engine.getHostname())) {
Method m_setServerNames = params.getClass().getMethod("setServerNames", List.class);
m_setServerNames.invoke(params,
Collections.<SNIServerName>singletonList(
new SNIHostName(engine.getHostname())));
}
}
/**
* Tries to return a Class reference of one of the supplied class names.
*/
private static Class<?> getClass(String... klasses) {
for (String klass : klasses) {
try {
return Class.forName(klass);
} catch (Exception ignored) {
//Ignored
}
}
return null;
}
public static void setEndpointIdentificationAlgorithm(
SSLParameters params, String endpointIdentificationAlgorithm) {
// TODO: implement this for unbundled
}
public static String getEndpointIdentificationAlgorithm(SSLParameters params) {
// TODO: implement this for unbundled
return null;
}
/**
* Helper function to unify calls to the different names used for each function taking a
* Socket, SSLEngine, or String (legacy Android).
*/
private static boolean checkTrusted(String methodName, X509TrustManager tm,
X509Certificate[] chain, String authType, Class<?> argumentClass,
Object argumentInstance) throws CertificateException {
// Use duck-typing to try and call the hostname-aware method if available.
try {
Method method = tm.getClass().getMethod(
methodName, X509Certificate[].class, String.class, argumentClass);
method.invoke(tm, chain, authType, argumentInstance);
return true;
} catch (NoSuchMethodException ignored) {
//Ignored
} catch (IllegalAccessException ignored) {
//Ignored
} catch (InvocationTargetException e) {
if (e.getCause() instanceof CertificateException) {
throw(CertificateException) e.getCause();
}
throw new RuntimeException(e.getCause());
}
return false;
}
@SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain,
String authType, AbstractConscryptSocket socket) throws CertificateException {
if (!checkTrusted("checkClientTrusted", tm, chain, authType, Socket.class, socket)
&& !checkTrusted("checkClientTrusted", tm, chain, authType, String.class,
socket.getHandshakeSession().getPeerHost())) {
tm.checkClientTrusted(chain, authType);
}
}
@SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
String authType, AbstractConscryptSocket socket) throws CertificateException {
if (!checkTrusted("checkServerTrusted", tm, chain, authType, Socket.class, socket)
&& !checkTrusted("checkServerTrusted", tm, chain, authType, String.class,
socket.getHandshakeSession().getPeerHost())) {
tm.checkServerTrusted(chain, authType);
}
}
@SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain,
String authType, ConscryptEngine engine) throws CertificateException {
if (!checkTrusted("checkClientTrusted", tm, chain, authType, SSLEngine.class, engine)
&& !checkTrusted("checkClientTrusted", tm, chain, authType, String.class,
engine.getHandshakeSession().getPeerHost())) {
tm.checkClientTrusted(chain, authType);
}
}
@SuppressLint("NewApi") // AbstractConscryptSocket defines getHandshakeSession()
public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
String authType, ConscryptEngine engine) throws CertificateException {
if (!checkTrusted("checkServerTrusted", tm, chain, authType, SSLEngine.class, engine)
&& !checkTrusted("checkServerTrusted", tm, chain, authType, String.class,
engine.getHandshakeSession().getPeerHost())) {
tm.checkServerTrusted(chain, authType);
}
}
/**
* Logs to the system EventLog system.
*/
@SuppressWarnings("LiteralClassName")
public static void logEvent(String message) {
try {
Class<?> processClass = Class.forName("android.os.Process");
Object processInstance = processClass.getDeclaredConstructor().newInstance();
Method myUidMethod = processClass.getMethod("myUid", (Class[]) null);
int uid = (Integer) myUidMethod.invoke(processInstance);
Class<?> eventLogClass = Class.forName("android.util.EventLog");
Object eventLogInstance = eventLogClass.getDeclaredConstructor().newInstance();
Method writeEventMethod =
eventLogClass.getMethod("writeEvent", Integer.TYPE, Object[].class);
writeEventMethod.invoke(eventLogInstance, 0x534e4554 /* SNET */,
new Object[] {"conscrypt", uid, message});
} catch (Exception e) {
// Fail silently
}
}
static SSLEngine wrapEngine(ConscryptEngine engine) {
// For now, don't wrap on Android.
return engine;
}
static SSLEngine unwrapEngine(SSLEngine engine) {
// For now, don't wrap on Android.
return engine;
}
static ConscryptEngineSocket createEngineSocket(SSLParametersImpl sslParameters)
throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8EngineSocket(sslParameters);
}
return new ConscryptEngineSocket(sslParameters);
}
static ConscryptEngineSocket createEngineSocket(String hostname, int port,
SSLParametersImpl sslParameters) throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8EngineSocket(hostname, port, sslParameters);
}
return new ConscryptEngineSocket(hostname, port, sslParameters);
}
static ConscryptEngineSocket createEngineSocket(InetAddress address, int port,
SSLParametersImpl sslParameters) throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8EngineSocket(address, port, sslParameters);
}
return new ConscryptEngineSocket(address, port, sslParameters);
}
static ConscryptEngineSocket createEngineSocket(String hostname, int port,
InetAddress clientAddress, int clientPort, SSLParametersImpl sslParameters)
throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8EngineSocket(hostname, port, clientAddress, clientPort, sslParameters);
}
return new ConscryptEngineSocket(hostname, port, clientAddress, clientPort, sslParameters);
}
static ConscryptEngineSocket createEngineSocket(InetAddress address, int port,
InetAddress clientAddress, int clientPort, SSLParametersImpl sslParameters)
throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8EngineSocket(address, port, clientAddress, clientPort, sslParameters);
}
return new ConscryptEngineSocket(address, port, clientAddress, clientPort, sslParameters);
}
static ConscryptEngineSocket createEngineSocket(Socket socket, String hostname, int port,
boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8EngineSocket(socket, hostname, port, autoClose, sslParameters);
}
return new ConscryptEngineSocket(socket, hostname, port, autoClose, sslParameters);
}
static ConscryptFileDescriptorSocket createFileDescriptorSocket(SSLParametersImpl sslParameters)
throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8FileDescriptorSocket(sslParameters);
}
return new ConscryptFileDescriptorSocket(sslParameters);
}
static ConscryptFileDescriptorSocket createFileDescriptorSocket(String hostname, int port,
SSLParametersImpl sslParameters) throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8FileDescriptorSocket(hostname, port, sslParameters);
}
return new ConscryptFileDescriptorSocket(hostname, port, sslParameters);
}
static ConscryptFileDescriptorSocket createFileDescriptorSocket(InetAddress address, int port,
SSLParametersImpl sslParameters) throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8FileDescriptorSocket(address, port, sslParameters);
}
return new ConscryptFileDescriptorSocket(address, port, sslParameters);
}
static ConscryptFileDescriptorSocket createFileDescriptorSocket(String hostname, int port,
InetAddress clientAddress, int clientPort, SSLParametersImpl sslParameters)
throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8FileDescriptorSocket(
hostname, port, clientAddress, clientPort, sslParameters);
}
return new ConscryptFileDescriptorSocket(
hostname, port, clientAddress, clientPort, sslParameters);
}
static ConscryptFileDescriptorSocket createFileDescriptorSocket(InetAddress address, int port,
InetAddress clientAddress, int clientPort, SSLParametersImpl sslParameters)
throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8FileDescriptorSocket(
address, port, clientAddress, clientPort, sslParameters);
}
return new ConscryptFileDescriptorSocket(
address, port, clientAddress, clientPort, sslParameters);
}
static ConscryptFileDescriptorSocket createFileDescriptorSocket(Socket socket, String hostname,
int port, boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8FileDescriptorSocket(socket, hostname, port, autoClose, sslParameters);
}
return new ConscryptFileDescriptorSocket(socket, hostname, port, autoClose, sslParameters);
}
/**
* Wrap the SocketFactory with the platform wrapper if needed for compatability.
*/
public static SSLSocketFactory wrapSocketFactoryIfNeeded(OpenSSLSocketFactoryImpl factory) {
if (Build.VERSION.SDK_INT < 19) {
return new PreKitKatPlatformOpenSSLSocketAdapterFactory(factory);
} else if (Build.VERSION.SDK_INT < 22) {
return new KitKatPlatformOpenSSLSocketAdapterFactory(factory);
}
return factory;
}
/**
* Convert from platform's GCMParameterSpec to our internal version.
*/
@SuppressWarnings("LiteralClassName")
public static GCMParameters fromGCMParameterSpec(AlgorithmParameterSpec params) {
Class<?> gcmSpecClass;
try {
gcmSpecClass = Class.forName("javax.crypto.spec.GCMParameterSpec");
} catch (ClassNotFoundException e) {
gcmSpecClass = null;
}
if (gcmSpecClass != null && gcmSpecClass.isAssignableFrom(params.getClass())) {
try {
int tLen;
byte[] iv;
Method getTLenMethod = gcmSpecClass.getMethod("getTLen");
Method getIVMethod = gcmSpecClass.getMethod("getIV");
tLen = (int) getTLenMethod.invoke(params);
iv = (byte[]) getIVMethod.invoke(params);
return new GCMParameters(tLen, iv);
} catch (NoSuchMethodException e) {
throw new RuntimeException("GCMParameterSpec lacks expected methods", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("GCMParameterSpec lacks expected methods", e);
} catch (InvocationTargetException e) {
throw new RuntimeException(
"Could not fetch GCM parameters", e.getTargetException());
}
}
return null;
}
/**
* Convert from an opaque AlgorithmParameters to the platform's GCMParameterSpec.
*/
@SuppressWarnings({"LiteralClassName", "unchecked"})
static AlgorithmParameterSpec fromGCMParameters(AlgorithmParameters params) {
Class<?> gcmSpecClass;
try {
gcmSpecClass = Class.forName("javax.crypto.spec.GCMParameterSpec");
} catch (ClassNotFoundException e) {
gcmSpecClass = null;
}
if (gcmSpecClass != null) {
try {
return params.getParameterSpec((Class) gcmSpecClass);
} catch (InvalidParameterSpecException e) {
return null;
}
}
return null;
}
/**
* Creates a platform version of {@code GCMParameterSpec}.
*/
@SuppressWarnings("LiteralClassName")
public static AlgorithmParameterSpec toGCMParameterSpec(int tagLenInBits, byte[] iv) {
Class<?> gcmSpecClass;
try {
gcmSpecClass = Class.forName("javax.crypto.spec.GCMParameterSpec");
} catch (ClassNotFoundException e) {
gcmSpecClass = null;
}
if (gcmSpecClass != null) {
try {
Constructor<?> constructor = gcmSpecClass.getConstructor(int.class, byte[].class);
return (AlgorithmParameterSpec) constructor.newInstance(tagLenInBits, iv);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException
| IllegalArgumentException e) {
logStackTraceSnippet("Can't find GCMParameterSpec class", e);
} catch (InvocationTargetException e) {
logStackTraceSnippet("Can't find GCMParameterSpec class", e.getCause());
}
}
return null;
}
/*
* CloseGuard functions.
*/
public static CloseGuard closeGuardGet() {
if (Build.VERSION.SDK_INT < 14) {
return null;
}
return CloseGuard.get();
}
public static void closeGuardOpen(Object guardObj, String message) {
if (Build.VERSION.SDK_INT < 14) {
return;
}
CloseGuard guard = (CloseGuard) guardObj;
guard.open(message);
}
public static void closeGuardClose(Object guardObj) {
if (Build.VERSION.SDK_INT < 14) {
return;
}
CloseGuard guard = (CloseGuard) guardObj;
guard.close();
}
public static void closeGuardWarnIfOpen(Object guardObj) {
if (Build.VERSION.SDK_INT < 14) {
return;
}
CloseGuard guard = (CloseGuard) guardObj;
guard.warnIfOpen();
}
/*
* BlockGuard functions.
*/
public static void blockGuardOnNetwork() {
BlockGuard.getThreadPolicy().onNetwork();
}
/**
* OID to Algorithm Name mapping.
*/
@SuppressWarnings("LiteralClassName")
public static String oidToAlgorithmName(String oid) {
// Old Harmony style
try {
Class<?> algNameMapperClass =
Class.forName("org.apache.harmony.security.utils.AlgNameMapper");
Method map2AlgNameMethod =
algNameMapperClass.getDeclaredMethod("map2AlgName", String.class);
map2AlgNameMethod.setAccessible(true);
return (String) map2AlgNameMethod.invoke(null, oid);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw(RuntimeException) cause;
} else if (cause instanceof Error) {
throw(Error) cause;
}
throw new RuntimeException(e);
} catch (Exception ignored) {
//Ignored
}
// Newer OpenJDK style
try {
Class<?> algorithmIdClass = Class.forName("sun.security.x509.AlgorithmId");
Method getMethod = algorithmIdClass.getDeclaredMethod("get", String.class);
getMethod.setAccessible(true);
Method getNameMethod = algorithmIdClass.getDeclaredMethod("getName");
getNameMethod.setAccessible(true);
Object algIdObj = getMethod.invoke(null, oid);
return (String) getNameMethod.invoke(algIdObj);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw(RuntimeException) cause;
} else if (cause instanceof Error) {
throw(Error) cause;
}
throw new RuntimeException(e);
} catch (Exception ignored) {
//Ignored
}
return oid;
}
/**
* Provides extended capabilities for the session if supported by the platform.
*/
public static SSLSession wrapSSLSession(ExternalSession sslSession) {
if (Build.VERSION.SDK_INT >= 24) {
return new Java8ExtendedSSLSession(sslSession);
}
return sslSession;
}
public static String getOriginalHostNameFromInetAddress(InetAddress addr) {
if (Build.VERSION.SDK_INT > 27) {
try {
Method getHolder = InetAddress.class.getDeclaredMethod("holder");
getHolder.setAccessible(true);
Method getOriginalHostName = Class.forName("java.net.InetAddress$InetAddressHolder")
.getDeclaredMethod("getOriginalHostName");
getOriginalHostName.setAccessible(true);
String originalHostName =
(String) getOriginalHostName.invoke(getHolder.invoke(addr));
if (originalHostName == null) {
return addr.getHostAddress();
}
return originalHostName;
} catch (InvocationTargetException e) {
throw new RuntimeException("Failed to get originalHostName", e);
} catch (ClassNotFoundException ignore) {
// passthrough and return addr.getHostAddress()
} catch (IllegalAccessException ignore) {
//Ignored
} catch (NoSuchMethodException ignore) {
//Ignored
}
}
return addr.getHostAddress();
}
/*
* Pre-Java-7 backward compatibility.
*/
public static String getHostStringFromInetSocketAddress(InetSocketAddress addr) {
if (Build.VERSION.SDK_INT > 23) {
try {
Method m_getHostString = InetSocketAddress.class.getDeclaredMethod("getHostString");
return (String) m_getHostString.invoke(addr);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (Exception ignored) {
//Ignored
}
}
return null;
}
// X509ExtendedTrustManager was added in API 24
static boolean supportsX509ExtendedTrustManager() {
return Build.VERSION.SDK_INT > 23;
}
/**
* Check if SCT verification is required for a given hostname.
*
* SCT Verification is enabled using {@code Security} properties.
* The "conscrypt.ct.enable" property must be true, as well as a per domain property.
* The reverse notation of the domain name, prefixed with "conscrypt.ct.enforce."
* is used as the property name.
* Basic globbing is also supported.
*
* For example, for the domain foo.bar.com, the following properties will be
* looked up, in order of precedence.
* - conscrypt.ct.enforce.com.bar.foo
* - conscrypt.ct.enforce.com.bar.*
* - conscrypt.ct.enforce.com.*
* - conscrypt.ct.enforce.*
*/
public static boolean isCTVerificationRequired(String hostname) {
if (hostname == null) {
return false;
}
// TODO: Use the platform version on platforms that support it
String property = Security.getProperty("conscrypt.ct.enable");
if (property == null || !Boolean.valueOf(property)) {
return false;
}
List<String> parts = Arrays.asList(hostname.split("\\."));
Collections.reverse(parts);
boolean enable = false;
String propertyName = "conscrypt.ct.enforce";
// The loop keeps going on even once we've found a match
// This allows for finer grained settings on subdomains
for (String part : parts) {
property = Security.getProperty(propertyName + ".*");
if (property != null) {
enable = Boolean.valueOf(property);
}
propertyName = propertyName + "." + part;
}
property = Security.getProperty(propertyName);
if (property != null) {
enable = Boolean.valueOf(property);
}
return enable;
}
static boolean supportsConscryptCertStore() {
return false;
}
static KeyStore getDefaultCertKeyStore() throws KeyStoreException {
KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
try {
keyStore.load(null, null);
} catch (IOException e) {
throw new KeyStoreException(e);
} catch (CertificateException e) {
throw new KeyStoreException(e);
} catch (NoSuchAlgorithmException e) {
throw new KeyStoreException(e);
}
return keyStore;
}
static ConscryptCertStore newDefaultCertStore() {
return null;
}
static CertBlocklist newDefaultBlocklist() {
return null;
}
static CTLogStore newDefaultLogStore() {
return null;
}
static CTPolicy newDefaultPolicy(CTLogStore logStore) {
return null;
}
static boolean serverNamePermitted(SSLParametersImpl parameters, String serverName) {
if (Build.VERSION.SDK_INT >= 24) {
return serverNamePermittedInternal(parameters, serverName);
}
return true;
}
@TargetApi(24)
private static boolean serverNamePermittedInternal(
SSLParametersImpl parameters, String serverName) {
Collection<SNIMatcher> sniMatchers = parameters.getSNIMatchers();
if (sniMatchers == null || sniMatchers.isEmpty()) {
return true;
}
for (SNIMatcher m : sniMatchers) {
boolean match = m.matches(new SNIHostName(serverName));
if (match) {
return true;
}
}
return false;
}
public static ConscryptHostnameVerifier getDefaultHostnameVerifier() {
return OkHostnameVerifier.strictInstance();
}
/**
* Returns milliseconds elapsed since boot, including time spent in sleep.
* @return long number of milliseconds elapsed since boot
*/
static long getMillisSinceBoot() {
return SystemClock.elapsedRealtime();
}
static void countTlsHandshake(
boolean success, String protocol, String cipherSuite, long durationLong) {
// Statsd classes appeared in SDK 30 and aren't available in earlier versions
if (Build.VERSION.SDK_INT >= 30) {
Protocol proto = Protocol.forName(protocol);
CipherSuite suite = CipherSuite.forName(cipherSuite);
int duration = (int) durationLong;
writeStats(success, proto.getId(), suite.getId(), duration);
}
}
@TargetApi(30)
private static void writeStats(
boolean success, int protocol, int cipherSuite, int duration) {
ConscryptStatsLog.write(ConscryptStatsLog.TLS_HANDSHAKE_REPORTED, success, protocol,
cipherSuite, duration, SOURCE_GMS);
}
public static boolean isJavaxCertificateSupported() {
return true;
}
}