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