| /* |
| * 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 android.os.Build; |
| import android.util.Log; |
| import dalvik.system.BlockGuard; |
| import dalvik.system.CloseGuard; |
| import java.io.FileDescriptor; |
| 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.security.InvalidKeyException; |
| import java.security.PrivateKey; |
| import java.security.cert.CertificateException; |
| import java.security.cert.X509Certificate; |
| import java.security.spec.AlgorithmParameterSpec; |
| import java.security.spec.ECParameterSpec; |
| |
| import javax.net.ssl.SSLEngine; |
| import javax.net.ssl.SSLParameters; |
| import javax.net.ssl.SSLSession; |
| import javax.net.ssl.SSLSocketFactory; |
| import javax.net.ssl.X509TrustManager; |
| |
| /** |
| * |
| */ |
| public 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) { |
| } |
| } |
| |
| public static void setup() { |
| } |
| |
| public static FileDescriptor getFileDescriptor(Socket s) { |
| if (Build.VERSION.SDK_INT >= 14) { |
| // Newer style in Android |
| try { |
| Method m_getFileDescriptor = Socket.class.getDeclaredMethod("getFileDescriptor$"); |
| m_getFileDescriptor.setAccessible(true); |
| return (FileDescriptor) m_getFileDescriptor.invoke(s); |
| } catch (Exception e) { |
| throw new RuntimeException(e.getCause()); |
| } |
| } else { |
| // Older style in Android (pre-ICS) |
| try { |
| Field f_impl = Socket.class.getDeclaredField("impl"); |
| f_impl.setAccessible(true); |
| Object socketImpl = f_impl.get(s); |
| Class<?> c_socketImpl = Class.forName("java.net.SocketImpl"); |
| Field f_fd = c_socketImpl.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(OpenSSLSocketImpl openSSLSocketImpl) { |
| return getFileDescriptor(openSSLSocketImpl); |
| } |
| |
| 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) { |
| } |
| } |
| |
| /* |
| * Call Os.setsockoptTimeval via reflection. |
| */ |
| public static void setSocketWriteTimeout(Socket s, long timeoutMillis) throws SocketException { |
| try { |
| Class<?> c_structTimeval = getClass("android.system.StructTimeval", |
| "libcore.io.StructTimeval"); |
| if (c_structTimeval == null) { |
| Log.w(TAG, "Cannot find StructTimeval; not setting socket write timeout"); |
| return; |
| } |
| |
| Method m_fromMillis = c_structTimeval.getDeclaredMethod("fromMillis", long.class); |
| Object timeval = m_fromMillis.invoke(null, timeoutMillis); |
| |
| Class<?> c_Libcore = Class.forName("libcore.io.Libcore"); |
| if (c_Libcore == null) { |
| Log.w(TAG, "Cannot find libcore.os.Libcore; not setting socket write timeout"); |
| return; |
| } |
| |
| Field f_os = c_Libcore.getField("os"); |
| Object instance_os = f_os.get(null); |
| |
| Class<?> c_osConstants = getClass("android.system.OsConstants", |
| "libcore.io.OsConstants"); |
| Field f_SOL_SOCKET = c_osConstants.getField("SOL_SOCKET"); |
| Field f_SO_SNDTIMEO = c_osConstants.getField("SO_SNDTIMEO"); |
| |
| Method m_setsockoptTimeval = instance_os.getClass().getMethod("setsockoptTimeval", |
| FileDescriptor.class, int.class, int.class, c_structTimeval); |
| |
| m_setsockoptTimeval.invoke(instance_os, getFileDescriptor(s), f_SOL_SOCKET.get(null), |
| f_SO_SNDTIMEO.get(null), timeval); |
| } catch (Exception e) { |
| Log.w(TAG, "Could not set socket write timeout: " + e.getMessage()); |
| } |
| } |
| |
| public static void setSSLParameters(SSLParameters params, SSLParametersImpl impl, |
| OpenSSLSocketImpl socket) { |
| // TODO fix for newer platform versions |
| } |
| |
| public static void getSSLParameters(SSLParameters params, SSLParametersImpl impl, |
| OpenSSLSocketImpl socket) { |
| // TODO fix for newer platform versions |
| } |
| |
| /** |
| * 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) { |
| } |
| } |
| 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 | IllegalAccessException ignored) { |
| } catch (InvocationTargetException e) { |
| if (e.getCause() instanceof CertificateException) { |
| throw (CertificateException) e.getCause(); |
| } |
| throw new RuntimeException(e.getCause()); |
| } |
| return false; |
| } |
| |
| public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain, |
| String authType, OpenSSLSocketImpl 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); |
| } |
| } |
| |
| public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain, |
| String authType, OpenSSLSocketImpl 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); |
| } |
| } |
| |
| public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain, |
| String authType, OpenSSLEngineImpl 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); |
| } |
| } |
| |
| public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain, |
| String authType, OpenSSLEngineImpl 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); |
| } |
| } |
| |
| /** |
| * Wraps an old AndroidOpenSSL key instance. This is not needed on platform |
| * builds since we didn't backport, so return null. This code is from |
| * Chromium's net/android/java/src/org/chromium/net/DefaultAndroidKeyStore.java |
| */ |
| public static OpenSSLKey wrapRsaKey(PrivateKey javaKey) { |
| // This fixup only applies to pre-JB-MR1 |
| if (Build.VERSION.SDK_INT >= 17) { |
| return null; |
| } |
| |
| // First, check that this is a proper instance of OpenSSLRSAPrivateKey |
| // or one of its sub-classes. |
| Class<?> superClass; |
| try { |
| superClass = Class |
| .forName("org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey"); |
| } catch (Exception e) { |
| // This may happen if the target device has a completely different |
| // implementation of the java.security APIs, compared to vanilla |
| // Android. Highly unlikely, but still possible. |
| Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e); |
| return null; |
| } |
| if (!superClass.isInstance(javaKey)) { |
| // This may happen if the PrivateKey was not created by the |
| // "AndroidOpenSSL" |
| // provider, which should be the default. That could happen if an |
| // OEM decided |
| // to implement a different default provider. Also highly unlikely. |
| Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" |
| + javaKey.getClass().getCanonicalName()); |
| return null; |
| } |
| |
| try { |
| // Use reflection to invoke the 'getOpenSSLKey()' method on |
| // the private key. This returns another Java object that wraps |
| // a native EVP_PKEY. Note that the method is final, so calling |
| // the superclass implementation is ok. |
| Method getKey = superClass.getDeclaredMethod("getOpenSSLKey"); |
| getKey.setAccessible(true); |
| Object opensslKey = null; |
| try { |
| opensslKey = getKey.invoke(javaKey); |
| } finally { |
| getKey.setAccessible(false); |
| } |
| if (opensslKey == null) { |
| // Bail when detecting OEM "enhancement". |
| Log.e(TAG, "Could not getOpenSSLKey on instance: " + javaKey.toString()); |
| return null; |
| } |
| |
| // Use reflection to invoke the 'getPkeyContext' method on the |
| // result of the getOpenSSLKey(). This is an 32-bit integer |
| // which is the address of an EVP_PKEY object. Note that this |
| // method these days returns a 64-bit long, but since this code |
| // path is used for older Android versions, it may still return |
| // a 32-bit int here. To be on the safe side, we cast the return |
| // value via Number rather than directly to Integer or Long. |
| Method getPkeyContext; |
| try { |
| getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext"); |
| } catch (Exception e) { |
| // Bail here too, something really not working as expected. |
| Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e); |
| return null; |
| } |
| getPkeyContext.setAccessible(true); |
| long evp_pkey = 0; |
| try { |
| evp_pkey = ((Number) getPkeyContext.invoke(opensslKey)).longValue(); |
| } finally { |
| getPkeyContext.setAccessible(false); |
| } |
| if (evp_pkey == 0) { |
| // The PrivateKey is probably rotten for some reason. |
| Log.e(TAG, "getPkeyContext() returned null"); |
| return null; |
| } |
| return new OpenSSLKey(evp_pkey); |
| } catch (Exception e) { |
| Log.e(TAG, "Error during conversion of privatekey instance: " + javaKey.toString(), e); |
| return null; |
| } |
| } |
| |
| /** |
| * Logs to the system EventLog system. |
| */ |
| public static void logEvent(String message) { |
| try { |
| Class processClass = Class.forName("android.os.Process"); |
| Object processInstance = processClass.newInstance(); |
| Method myUidMethod = processClass.getMethod("myUid", (Class[]) null); |
| int uid = (Integer) myUidMethod.invoke(processInstance); |
| |
| Class eventLogClass = Class.forName("android.util.EventLog"); |
| Object eventLogInstance = eventLogClass.newInstance(); |
| Method writeEventMethod = eventLogClass.getMethod("writeEvent", |
| new Class[] { Integer.TYPE, Object[].class }); |
| writeEventMethod.invoke(eventLogInstance, 0x534e4554 /* SNET */, |
| new Object[] { "conscrypt", uid, message }); |
| } catch (Exception e) { |
| // Fail silently |
| } |
| } |
| |
| /** |
| * Returns true if the supplied hostname is an literal IP address. |
| */ |
| public static boolean isLiteralIpAddress(String hostname) { |
| try { |
| Method m_isNumeric = InetAddress.class.getMethod("isNumeric", String.class); |
| return (Boolean) m_isNumeric.invoke(null, hostname); |
| } catch (Exception ignored) { |
| } |
| |
| return AddressUtils.isLiteralIpAddress(hostname); |
| } |
| |
| /** |
| * 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. |
| */ |
| 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 | 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; |
| } |
| |
| /** |
| * Creates a platform version of {@code GCMParameterSpec}. |
| */ |
| 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) { |
| e.printStackTrace(); |
| } catch (InvocationTargetException e) { |
| e.getCause().printStackTrace(); |
| } |
| } |
| 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. |
| */ |
| 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) { |
| } |
| |
| // 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) { |
| } |
| |
| return oid; |
| } |
| |
| /* |
| * Pre-Java 8 backward compatibility. |
| */ |
| |
| public static SSLSession wrapSSLSession(OpenSSLSessionImpl sslSession) { |
| if (Build.VERSION.SDK_INT <= 23) { |
| return sslSession; |
| } else { |
| return new OpenSSLExtendedSessionImpl(sslSession); |
| } |
| } |
| |
| /* |
| * 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) { |
| } |
| } |
| return null; |
| } |
| } |