1/* 2 * Copyright 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package org.conscrypt; 18 19import android.os.Build; 20import android.util.Log; 21import dalvik.system.BlockGuard; 22import dalvik.system.CloseGuard; 23import java.io.FileDescriptor; 24import java.lang.reflect.Constructor; 25import java.lang.reflect.Field; 26import java.lang.reflect.InvocationTargetException; 27import java.lang.reflect.Method; 28import java.net.InetAddress; 29import java.net.InetSocketAddress; 30import java.net.Socket; 31import java.net.SocketException; 32import java.security.InvalidKeyException; 33import java.security.PrivateKey; 34import java.security.cert.CertificateException; 35import java.security.cert.X509Certificate; 36import java.security.spec.AlgorithmParameterSpec; 37import java.security.spec.ECParameterSpec; 38 39import javax.net.ssl.SSLEngine; 40import javax.net.ssl.SSLParameters; 41import javax.net.ssl.SSLSession; 42import javax.net.ssl.SSLSocketFactory; 43import javax.net.ssl.X509TrustManager; 44 45/** 46 * 47 */ 48public class Platform { 49 private static final String TAG = "Conscrypt"; 50 51 private static Method m_getCurveName; 52 static { 53 try { 54 m_getCurveName = ECParameterSpec.class.getDeclaredMethod("getCurveName"); 55 m_getCurveName.setAccessible(true); 56 } catch (Exception ignored) { 57 } 58 } 59 60 public static void setup() { 61 } 62 63 public static FileDescriptor getFileDescriptor(Socket s) { 64 if (Build.VERSION.SDK_INT >= 14) { 65 // Newer style in Android 66 try { 67 Method m_getFileDescriptor = Socket.class.getDeclaredMethod("getFileDescriptor$"); 68 m_getFileDescriptor.setAccessible(true); 69 return (FileDescriptor) m_getFileDescriptor.invoke(s); 70 } catch (Exception e) { 71 throw new RuntimeException(e.getCause()); 72 } 73 } else { 74 // Older style in Android (pre-ICS) 75 try { 76 Field f_impl = Socket.class.getDeclaredField("impl"); 77 f_impl.setAccessible(true); 78 Object socketImpl = f_impl.get(s); 79 Class<?> c_socketImpl = Class.forName("java.net.SocketImpl"); 80 Field f_fd = c_socketImpl.getDeclaredField("fd"); 81 f_fd.setAccessible(true); 82 return (FileDescriptor) f_fd.get(socketImpl); 83 } catch (Exception e) { 84 throw new RuntimeException("Can't get FileDescriptor from socket", e); 85 } 86 } 87 } 88 89 public static FileDescriptor getFileDescriptorFromSSLSocket(OpenSSLSocketImpl openSSLSocketImpl) { 90 return getFileDescriptor(openSSLSocketImpl); 91 } 92 93 public static String getCurveName(ECParameterSpec spec) { 94 if (m_getCurveName == null) { 95 return null; 96 } 97 try { 98 return (String) m_getCurveName.invoke(spec); 99 } catch (Exception e) { 100 return null; 101 } 102 } 103 104 public static void setCurveName(ECParameterSpec spec, String curveName) { 105 try { 106 Method setCurveName = spec.getClass().getDeclaredMethod("setCurveName", String.class); 107 setCurveName.invoke(spec, curveName); 108 } catch (Exception ignored) { 109 } 110 } 111 112 /* 113 * Call Os.setsockoptTimeval via reflection. 114 */ 115 public static void setSocketWriteTimeout(Socket s, long timeoutMillis) throws SocketException { 116 try { 117 Class<?> c_structTimeval = getClass("android.system.StructTimeval", 118 "libcore.io.StructTimeval"); 119 if (c_structTimeval == null) { 120 Log.w(TAG, "Cannot find StructTimeval; not setting socket write timeout"); 121 return; 122 } 123 124 Method m_fromMillis = c_structTimeval.getDeclaredMethod("fromMillis", long.class); 125 Object timeval = m_fromMillis.invoke(null, timeoutMillis); 126 127 Class<?> c_Libcore = Class.forName("libcore.io.Libcore"); 128 if (c_Libcore == null) { 129 Log.w(TAG, "Cannot find libcore.os.Libcore; not setting socket write timeout"); 130 return; 131 } 132 133 Field f_os = c_Libcore.getField("os"); 134 Object instance_os = f_os.get(null); 135 136 Class<?> c_osConstants = getClass("android.system.OsConstants", 137 "libcore.io.OsConstants"); 138 Field f_SOL_SOCKET = c_osConstants.getField("SOL_SOCKET"); 139 Field f_SO_SNDTIMEO = c_osConstants.getField("SO_SNDTIMEO"); 140 141 Method m_setsockoptTimeval = instance_os.getClass().getMethod("setsockoptTimeval", 142 FileDescriptor.class, int.class, int.class, c_structTimeval); 143 144 m_setsockoptTimeval.invoke(instance_os, getFileDescriptor(s), f_SOL_SOCKET.get(null), 145 f_SO_SNDTIMEO.get(null), timeval); 146 } catch (Exception e) { 147 Log.w(TAG, "Could not set socket write timeout: " + e.getMessage()); 148 } 149 } 150 151 public static void setSSLParameters(SSLParameters params, SSLParametersImpl impl, 152 OpenSSLSocketImpl socket) { 153 // TODO fix for newer platform versions 154 } 155 156 public static void getSSLParameters(SSLParameters params, SSLParametersImpl impl, 157 OpenSSLSocketImpl socket) { 158 // TODO fix for newer platform versions 159 } 160 161 /** 162 * Tries to return a Class reference of one of the supplied class names. 163 */ 164 private static Class<?> getClass(String... klasses) { 165 for (String klass : klasses) { 166 try { 167 return Class.forName(klass); 168 } catch (Exception ignored) { 169 } 170 } 171 return null; 172 } 173 174 public static void setEndpointIdentificationAlgorithm(SSLParameters params, 175 String endpointIdentificationAlgorithm) { 176 // TODO: implement this for unbundled 177 } 178 179 public static String getEndpointIdentificationAlgorithm(SSLParameters params) { 180 // TODO: implement this for unbundled 181 return null; 182 } 183 184 /** 185 * Helper function to unify calls to the different names used for each function taking a 186 * Socket, SSLEngine, or String (legacy Android). 187 */ 188 private static boolean checkTrusted(String methodName, X509TrustManager tm, 189 X509Certificate[] chain, String authType, Class<?> argumentClass, 190 Object argumentInstance) throws CertificateException { 191 // Use duck-typing to try and call the hostname-aware method if available. 192 try { 193 Method method = tm.getClass().getMethod(methodName, 194 X509Certificate[].class, 195 String.class, 196 argumentClass); 197 method.invoke(tm, chain, authType, argumentInstance); 198 return true; 199 } catch (NoSuchMethodException | IllegalAccessException ignored) { 200 } catch (InvocationTargetException e) { 201 if (e.getCause() instanceof CertificateException) { 202 throw (CertificateException) e.getCause(); 203 } 204 throw new RuntimeException(e.getCause()); 205 } 206 return false; 207 } 208 209 public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain, 210 String authType, OpenSSLSocketImpl socket) throws CertificateException { 211 if (!checkTrusted("checkClientTrusted", tm, chain, authType, Socket.class, socket) 212 && !checkTrusted("checkClientTrusted", tm, chain, authType, String.class, 213 socket.getHandshakeSession().getPeerHost())) { 214 tm.checkClientTrusted(chain, authType); 215 } 216 } 217 218 public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain, 219 String authType, OpenSSLSocketImpl socket) throws CertificateException { 220 if (!checkTrusted("checkServerTrusted", tm, chain, authType, Socket.class, socket) 221 && !checkTrusted("checkServerTrusted", tm, chain, authType, String.class, 222 socket.getHandshakeSession().getPeerHost())) { 223 tm.checkServerTrusted(chain, authType); 224 } 225 } 226 227 public static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain, 228 String authType, OpenSSLEngineImpl engine) throws CertificateException { 229 if (!checkTrusted("checkClientTrusted", tm, chain, authType, SSLEngine.class, engine) 230 && !checkTrusted("checkClientTrusted", tm, chain, authType, String.class, 231 engine.getHandshakeSession().getPeerHost())) { 232 tm.checkClientTrusted(chain, authType); 233 } 234 } 235 236 public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain, 237 String authType, OpenSSLEngineImpl engine) throws CertificateException { 238 if (!checkTrusted("checkServerTrusted", tm, chain, authType, SSLEngine.class, engine) 239 && !checkTrusted("checkServerTrusted", tm, chain, authType, String.class, 240 engine.getHandshakeSession().getPeerHost())) { 241 tm.checkServerTrusted(chain, authType); 242 } 243 } 244 245 /** 246 * Wraps an old AndroidOpenSSL key instance. This is not needed on platform 247 * builds since we didn't backport, so return null. This code is from 248 * Chromium's net/android/java/src/org/chromium/net/DefaultAndroidKeyStore.java 249 */ 250 public static OpenSSLKey wrapRsaKey(PrivateKey javaKey) { 251 // This fixup only applies to pre-JB-MR1 252 if (Build.VERSION.SDK_INT >= 17) { 253 return null; 254 } 255 256 // First, check that this is a proper instance of OpenSSLRSAPrivateKey 257 // or one of its sub-classes. 258 Class<?> superClass; 259 try { 260 superClass = Class 261 .forName("org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey"); 262 } catch (Exception e) { 263 // This may happen if the target device has a completely different 264 // implementation of the java.security APIs, compared to vanilla 265 // Android. Highly unlikely, but still possible. 266 Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e); 267 return null; 268 } 269 if (!superClass.isInstance(javaKey)) { 270 // This may happen if the PrivateKey was not created by the 271 // "AndroidOpenSSL" 272 // provider, which should be the default. That could happen if an 273 // OEM decided 274 // to implement a different default provider. Also highly unlikely. 275 Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" 276 + javaKey.getClass().getCanonicalName()); 277 return null; 278 } 279 280 try { 281 // Use reflection to invoke the 'getOpenSSLKey()' method on 282 // the private key. This returns another Java object that wraps 283 // a native EVP_PKEY. Note that the method is final, so calling 284 // the superclass implementation is ok. 285 Method getKey = superClass.getDeclaredMethod("getOpenSSLKey"); 286 getKey.setAccessible(true); 287 Object opensslKey = null; 288 try { 289 opensslKey = getKey.invoke(javaKey); 290 } finally { 291 getKey.setAccessible(false); 292 } 293 if (opensslKey == null) { 294 // Bail when detecting OEM "enhancement". 295 Log.e(TAG, "Could not getOpenSSLKey on instance: " + javaKey.toString()); 296 return null; 297 } 298 299 // Use reflection to invoke the 'getPkeyContext' method on the 300 // result of the getOpenSSLKey(). This is an 32-bit integer 301 // which is the address of an EVP_PKEY object. Note that this 302 // method these days returns a 64-bit long, but since this code 303 // path is used for older Android versions, it may still return 304 // a 32-bit int here. To be on the safe side, we cast the return 305 // value via Number rather than directly to Integer or Long. 306 Method getPkeyContext; 307 try { 308 getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext"); 309 } catch (Exception e) { 310 // Bail here too, something really not working as expected. 311 Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e); 312 return null; 313 } 314 getPkeyContext.setAccessible(true); 315 long evp_pkey = 0; 316 try { 317 evp_pkey = ((Number) getPkeyContext.invoke(opensslKey)).longValue(); 318 } finally { 319 getPkeyContext.setAccessible(false); 320 } 321 if (evp_pkey == 0) { 322 // The PrivateKey is probably rotten for some reason. 323 Log.e(TAG, "getPkeyContext() returned null"); 324 return null; 325 } 326 return new OpenSSLKey(evp_pkey); 327 } catch (Exception e) { 328 Log.e(TAG, "Error during conversion of privatekey instance: " + javaKey.toString(), e); 329 return null; 330 } 331 } 332 333 /** 334 * Logs to the system EventLog system. 335 */ 336 public static void logEvent(String message) { 337 try { 338 Class processClass = Class.forName("android.os.Process"); 339 Object processInstance = processClass.newInstance(); 340 Method myUidMethod = processClass.getMethod("myUid", (Class[]) null); 341 int uid = (Integer) myUidMethod.invoke(processInstance); 342 343 Class eventLogClass = Class.forName("android.util.EventLog"); 344 Object eventLogInstance = eventLogClass.newInstance(); 345 Method writeEventMethod = eventLogClass.getMethod("writeEvent", 346 new Class[] { Integer.TYPE, Object[].class }); 347 writeEventMethod.invoke(eventLogInstance, 0x534e4554 /* SNET */, 348 new Object[] { "conscrypt", uid, message }); 349 } catch (Exception e) { 350 // Fail silently 351 } 352 } 353 354 /** 355 * Returns true if the supplied hostname is an literal IP address. 356 */ 357 public static boolean isLiteralIpAddress(String hostname) { 358 try { 359 Method m_isNumeric = InetAddress.class.getMethod("isNumeric", String.class); 360 return (Boolean) m_isNumeric.invoke(null, hostname); 361 } catch (Exception ignored) { 362 } 363 364 return AddressUtils.isLiteralIpAddress(hostname); 365 } 366 367 /** 368 * Wrap the SocketFactory with the platform wrapper if needed for compatability. 369 */ 370 public static SSLSocketFactory wrapSocketFactoryIfNeeded(OpenSSLSocketFactoryImpl factory) { 371 if (Build.VERSION.SDK_INT < 19) { 372 return new PreKitKatPlatformOpenSSLSocketAdapterFactory(factory); 373 } else if (Build.VERSION.SDK_INT < 22) { 374 return new KitKatPlatformOpenSSLSocketAdapterFactory(factory); 375 } 376 return factory; 377 } 378 379 /** 380 * Convert from platform's GCMParameterSpec to our internal version. 381 */ 382 public static GCMParameters fromGCMParameterSpec(AlgorithmParameterSpec params) { 383 Class<?> gcmSpecClass; 384 try { 385 gcmSpecClass = Class.forName("javax.crypto.spec.GCMParameterSpec"); 386 } catch (ClassNotFoundException e) { 387 gcmSpecClass = null; 388 } 389 390 if (gcmSpecClass != null && gcmSpecClass.isAssignableFrom(params.getClass())) { 391 try { 392 int tLen; 393 byte[] iv; 394 395 Method getTLenMethod = gcmSpecClass.getMethod("getTLen"); 396 Method getIVMethod = gcmSpecClass.getMethod("getIV"); 397 tLen = (int) getTLenMethod.invoke(params); 398 iv = (byte[]) getIVMethod.invoke(params); 399 400 return new GCMParameters(tLen, iv); 401 } catch (NoSuchMethodException | IllegalAccessException e) { 402 throw new RuntimeException("GCMParameterSpec lacks expected methods", e); 403 } catch (InvocationTargetException e) { 404 throw new RuntimeException("Could not fetch GCM parameters", e.getTargetException()); 405 } 406 } 407 return null; 408 } 409 410 /** 411 * Creates a platform version of {@code GCMParameterSpec}. 412 */ 413 public static AlgorithmParameterSpec toGCMParameterSpec(int tagLenInBits, byte[] iv) { 414 Class<?> gcmSpecClass; 415 try { 416 gcmSpecClass = Class.forName("javax.crypto.spec.GCMParameterSpec"); 417 } catch (ClassNotFoundException e) { 418 gcmSpecClass = null; 419 } 420 421 if (gcmSpecClass != null) { 422 try { 423 Constructor<?> constructor = gcmSpecClass.getConstructor(int.class, byte[].class); 424 return (AlgorithmParameterSpec) constructor.newInstance(tagLenInBits, iv); 425 } catch (NoSuchMethodException | InstantiationException | IllegalAccessException 426 | IllegalArgumentException e) { 427 e.printStackTrace(); 428 } catch (InvocationTargetException e) { 429 e.getCause().printStackTrace(); 430 } 431 } 432 return null; 433 } 434 435 /* 436 * CloseGuard functions. 437 */ 438 439 public static CloseGuard closeGuardGet() { 440 if (Build.VERSION.SDK_INT < 14) { 441 return null; 442 } 443 444 return CloseGuard.get(); 445 } 446 447 public static void closeGuardOpen(Object guardObj, String message) { 448 if (Build.VERSION.SDK_INT < 14) { 449 return; 450 } 451 452 CloseGuard guard = (CloseGuard) guardObj; 453 guard.open(message); 454 } 455 456 public static void closeGuardClose(Object guardObj) { 457 if (Build.VERSION.SDK_INT < 14) { 458 return; 459 } 460 461 CloseGuard guard = (CloseGuard) guardObj; 462 guard.close(); 463 } 464 465 public static void closeGuardWarnIfOpen(Object guardObj) { 466 if (Build.VERSION.SDK_INT < 14) { 467 return; 468 } 469 470 CloseGuard guard = (CloseGuard) guardObj; 471 guard.warnIfOpen(); 472 } 473 474 /* 475 * BlockGuard functions. 476 */ 477 478 public static void blockGuardOnNetwork() { 479 BlockGuard.getThreadPolicy().onNetwork(); 480 } 481 482 /** 483 * OID to Algorithm Name mapping. 484 */ 485 public static String oidToAlgorithmName(String oid) { 486 // Old Harmony style 487 try { 488 Class<?> algNameMapperClass = Class.forName( 489 "org.apache.harmony.security.utils.AlgNameMapper"); 490 Method map2AlgNameMethod = algNameMapperClass.getDeclaredMethod("map2AlgName", 491 String.class); 492 map2AlgNameMethod.setAccessible(true); 493 return (String) map2AlgNameMethod.invoke(null, oid); 494 } catch (InvocationTargetException e) { 495 Throwable cause = e.getCause(); 496 if (cause instanceof RuntimeException) { 497 throw (RuntimeException) cause; 498 } else if (cause instanceof Error) { 499 throw (Error) cause; 500 } 501 throw new RuntimeException(e); 502 } catch (Exception ignored) { 503 } 504 505 // Newer OpenJDK style 506 try { 507 Class<?> algorithmIdClass = Class.forName("sun.security.x509.AlgorithmId"); 508 Method getMethod = algorithmIdClass.getDeclaredMethod("get", String.class); 509 getMethod.setAccessible(true); 510 Method getNameMethod = algorithmIdClass.getDeclaredMethod("getName"); 511 getNameMethod.setAccessible(true); 512 513 Object algIdObj = getMethod.invoke(null, oid); 514 return (String) getNameMethod.invoke(algIdObj); 515 } catch (InvocationTargetException e) { 516 Throwable cause = e.getCause(); 517 if (cause instanceof RuntimeException) { 518 throw (RuntimeException) cause; 519 } else if (cause instanceof Error) { 520 throw (Error) cause; 521 } 522 throw new RuntimeException(e); 523 } catch (Exception ignored) { 524 } 525 526 return oid; 527 } 528 529 /* 530 * Pre-Java 8 backward compatibility. 531 */ 532 533 public static SSLSession wrapSSLSession(OpenSSLSessionImpl sslSession) { 534 if (Build.VERSION.SDK_INT <= 23) { 535 return sslSession; 536 } else { 537 return new OpenSSLExtendedSessionImpl(sslSession); 538 } 539 } 540 541 /* 542 * Pre-Java-7 backward compatibility. 543 */ 544 545 public static String getHostStringFromInetSocketAddress(InetSocketAddress addr) { 546 if (Build.VERSION.SDK_INT > 23) { 547 try { 548 Method m_getHostString = InetSocketAddress.class 549 .getDeclaredMethod("getHostString"); 550 return (String) m_getHostString.invoke(addr); 551 } catch (InvocationTargetException e) { 552 throw new RuntimeException(e); 553 } catch (Exception ignored) { 554 } 555 } 556 return null; 557 } 558} 559