summaryrefslogtreecommitdiff
path: root/chromium/net/android/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/android/java/src')
-rw-r--r--chromium/net/android/java/src/org/chromium/net/AndroidKeyStore.java308
-rw-r--r--chromium/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java232
-rw-r--r--chromium/net/android/java/src/org/chromium/net/GURLUtils.java38
-rw-r--r--chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java224
-rw-r--r--chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java196
-rw-r--r--chromium/net/android/java/src/org/chromium/net/ProxyChangeListener.java115
-rw-r--r--chromium/net/android/java/src/org/chromium/net/X509Util.java233
7 files changed, 1346 insertions, 0 deletions
diff --git a/chromium/net/android/java/src/org/chromium/net/AndroidKeyStore.java b/chromium/net/android/java/src/org/chromium/net/AndroidKeyStore.java
new file mode 100644
index 00000000000..2edaa895b7e
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/AndroidKeyStore.java
@@ -0,0 +1,308 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.security.interfaces.DSAKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.spec.ECParameterSpec;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.net.PrivateKeyType;;
+
+@JNINamespace("net::android")
+public class AndroidKeyStore {
+
+ private static final String TAG = "AndroidKeyStore";
+
+ ////////////////////////////////////////////////////////////////////
+ //
+ // Message signing support.
+
+ /**
+ * Returns the public modulus of a given RSA private key as a byte
+ * buffer.
+ * This can be used by native code to convert the modulus into
+ * an OpenSSL BIGNUM object. Required to craft a custom native RSA
+ * object where RSA_size() works as expected.
+ *
+ * @param key A PrivateKey instance, must implement RSAKey.
+ * @return A byte buffer corresponding to the modulus. This is
+ * big-endian representation of a BigInteger.
+ */
+ @CalledByNative
+ public static byte[] getRSAKeyModulus(PrivateKey key) {
+ if (key instanceof RSAKey) {
+ return ((RSAKey) key).getModulus().toByteArray();
+ } else {
+ Log.w(TAG, "Not a RSAKey instance!");
+ return null;
+ }
+ }
+
+ /**
+ * Returns the 'Q' parameter of a given DSA private key as a byte
+ * buffer.
+ * This can be used by native code to convert it into an OpenSSL BIGNUM
+ * object where DSA_size() works as expected.
+ *
+ * @param key A PrivateKey instance. Must implement DSAKey.
+ * @return A byte buffer corresponding to the Q parameter. This is
+ * a big-endian representation of a BigInteger.
+ */
+ @CalledByNative
+ public static byte[] getDSAKeyParamQ(PrivateKey key) {
+ if (key instanceof DSAKey) {
+ DSAParams params = ((DSAKey) key).getParams();
+ return params.getQ().toByteArray();
+ } else {
+ Log.w(TAG, "Not a DSAKey instance!");
+ return null;
+ }
+ }
+
+ /**
+ * Returns the 'order' parameter of a given ECDSA private key as a
+ * a byte buffer.
+ * @param key A PrivateKey instance. Must implement ECKey.
+ * @return A byte buffer corresponding to the 'order' parameter.
+ * This is a big-endian representation of a BigInteger.
+ */
+ @CalledByNative
+ public static byte[] getECKeyOrder(PrivateKey key) {
+ if (key instanceof ECKey) {
+ ECParameterSpec params = ((ECKey) key).getParams();
+ return params.getOrder().toByteArray();
+ } else {
+ Log.w(TAG, "Not an ECKey instance!");
+ return null;
+ }
+ }
+
+ /**
+ * Returns the encoded data corresponding to a given PrivateKey.
+ * Note that this will fail for platform keys on Android 4.0.4
+ * and higher. It can be used on 4.0.3 and older platforms to
+ * route around the platform bug described below.
+ * @param key A PrivateKey instance
+ * @return encoded key as PKCS#8 byte array, can be null.
+ */
+ @CalledByNative
+ public static byte[] getPrivateKeyEncodedBytes(PrivateKey key) {
+ return key.getEncoded();
+ }
+
+ /**
+ * Sign a given message with a given PrivateKey object. This method
+ * shall only be used to implement signing in the context of SSL
+ * client certificate support.
+ *
+ * The message will actually be a hash, computed and padded by OpenSSL,
+ * itself, depending on the type of the key. The result should match
+ * exactly what the vanilla implementations of the following OpenSSL
+ * function calls do:
+ *
+ * - For a RSA private key, this should be equivalent to calling
+ * RSA_sign(NDI_md5_sha1,....), i.e. it must generate a raw RSA
+ * signature. The message must a combined, 36-byte MD5+SHA1 message
+ * digest padded to the length of the modulus using PKCS#1 padding.
+ *
+ * - For a DSA and ECDSA private keys, this should be equivalent to
+ * calling DSA_sign(0,...) and ECDSA_sign(0,...) respectively. The
+ * message must be a 20-byte SHA1 hash and the function shall
+ * compute a direct DSA/ECDSA signature for it.
+ *
+ * @param privateKey The PrivateKey handle.
+ * @param message The message to sign.
+ * @return signature as a byte buffer.
+ *
+ * Important: Due to a platform bug, this function will always fail on
+ * Android < 4.2 for RSA PrivateKey objects. See the
+ * getOpenSSLHandleForPrivateKey() below for work-around.
+ */
+ @CalledByNative
+ public static byte[] rawSignDigestWithPrivateKey(PrivateKey privateKey,
+ byte[] message) {
+ // Get the Signature for this key.
+ Signature signature = null;
+ // Hint: Algorithm names come from:
+ // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
+ try {
+ if (privateKey instanceof RSAPrivateKey) {
+ // IMPORTANT: Due to a platform bug, this will throw NoSuchAlgorithmException
+ // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher.
+ // See https://android-review.googlesource.com/#/c/40352/
+ signature = Signature.getInstance("NONEwithRSA");
+ } else if (privateKey instanceof DSAPrivateKey) {
+ signature = Signature.getInstance("NONEwithDSA");
+ } else if (privateKey instanceof ECPrivateKey) {
+ signature = Signature.getInstance("NONEwithECDSA");
+ }
+ } catch (NoSuchAlgorithmException e) {
+ ;
+ }
+
+ if (signature == null) {
+ Log.e(TAG, "Unsupported private key algorithm: " + privateKey.getAlgorithm());
+ return null;
+ }
+
+ // Sign the message.
+ try {
+ signature.initSign(privateKey);
+ signature.update(message);
+ return signature.sign();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while signing message with " + privateKey.getAlgorithm() +
+ " private key: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Return the type of a given PrivateKey object. This is an integer
+ * that maps to one of the values defined by org.chromium.net.PrivateKeyType,
+ * which is itself auto-generated from net/android/private_key_type_list.h
+ * @param privateKey The PrivateKey handle
+ * @return key type, or PrivateKeyType.INVALID if unknown.
+ */
+ @CalledByNative
+ public static int getPrivateKeyType(PrivateKey privateKey) {
+ if (privateKey instanceof RSAPrivateKey)
+ return PrivateKeyType.RSA;
+ if (privateKey instanceof DSAPrivateKey)
+ return PrivateKeyType.DSA;
+ if (privateKey instanceof ECPrivateKey)
+ return PrivateKeyType.ECDSA;
+ else
+ return PrivateKeyType.INVALID;
+ }
+
+ /**
+ * Return the system EVP_PKEY handle corresponding to a given PrivateKey
+ * object, obtained through reflection.
+ *
+ * This shall only be used when the "NONEwithRSA" signature is not
+ * available, as described in rawSignDigestWithPrivateKey(). I.e.
+ * never use this on Android 4.2 or higher.
+ *
+ * This can only work in Android 4.0.4 and higher, for older versions
+ * of the platform (e.g. 4.0.3), there is no system OpenSSL EVP_PKEY,
+ * but the private key contents can be retrieved directly with
+ * the getEncoded() method.
+ *
+ * This assumes that the target device uses a vanilla AOSP
+ * implementation of its java.security classes, which is also
+ * based on OpenSSL (fortunately, no OEM has apperently changed to
+ * a different implementation, according to the Android team).
+ *
+ * Note that the object returned was created with the platform version
+ * of OpenSSL, and _not_ the one that comes with Chromium. Whether the
+ * object can be used safely with the Chromium OpenSSL library depends
+ * on differences between their actual ABI / implementation details.
+ *
+ * To better understand what's going on below, please refer to the
+ * following source files in the Android 4.0.4 and 4.1 source trees:
+ * libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
+ * libcore/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+ *
+ * @param privateKey The PrivateKey handle.
+ * @return The EVP_PKEY handle, as a 32-bit integer (0 if not available)
+ */
+ @CalledByNative
+ public static int getOpenSSLHandleForPrivateKey(PrivateKey privateKey) {
+ // Sanity checks
+ if (privateKey == null) {
+ Log.e(TAG, "privateKey == null");
+ return 0;
+ }
+ if (!(privateKey instanceof RSAPrivateKey)) {
+ Log.e(TAG, "does not implement RSAPrivateKey");
+ return 0;
+ }
+ // 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 0;
+ }
+ if (!superClass.isInstance(privateKey)) {
+ // 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:" +
+ privateKey.getClass().getCanonicalName());
+ return 0;
+ }
+
+ 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(privateKey);
+ } finally {
+ getKey.setAccessible(false);
+ }
+ if (opensslKey == null) {
+ // Bail when detecting OEM "enhancement".
+ Log.e(TAG, "getOpenSSLKey() returned null");
+ return 0;
+ }
+
+ // 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.
+ 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 0;
+ }
+ getPkeyContext.setAccessible(true);
+ int evp_pkey = 0;
+ try {
+ evp_pkey = (Integer) getPkeyContext.invoke(opensslKey);
+ } finally {
+ getPkeyContext.setAccessible(false);
+ }
+ if (evp_pkey == 0) {
+ // The PrivateKey is probably rotten for some reason.
+ Log.e(TAG, "getPkeyContext() returned null");
+ }
+ return evp_pkey;
+
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e);
+ return 0;
+ }
+ }
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java b/chromium/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
new file mode 100644
index 00000000000..95752cca8b2
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
@@ -0,0 +1,232 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.security.KeyChain;
+import android.util.Log;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.CalledByNativeUnchecked;
+import org.chromium.net.CertVerifyResultAndroid;
+import org.chromium.net.CertificateMimeType;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.URLConnection;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.Enumeration;
+
+/**
+ * This class implements net utilities required by the net component.
+ */
+class AndroidNetworkLibrary {
+
+ private static final String TAG = "AndroidNetworkLibrary";
+
+ /**
+ * Stores the key pair through the CertInstaller activity.
+ * @param context: current application context.
+ * @param public_key: The public key bytes as DER-encoded SubjectPublicKeyInfo (X.509)
+ * @param private_key: The private key as DER-encoded PrivateKeyInfo (PKCS#8).
+ * @return: true on success, false on failure.
+ *
+ * Note that failure means that the function could not launch the CertInstaller
+ * activity. Whether the keys are valid or properly installed will be indicated
+ * by the CertInstaller UI itself.
+ */
+ @CalledByNative
+ static public boolean storeKeyPair(Context context, byte[] public_key, byte[] private_key) {
+ // TODO(digit): Use KeyChain official extra values to pass the public and private
+ // keys when they're available. The "KEY" and "PKEY" hard-coded constants were taken
+ // from the platform sources, since there are no official KeyChain.EXTRA_XXX definitions
+ // for them. b/5859651
+ try {
+ Intent intent = KeyChain.createInstallIntent();
+ intent.putExtra("PKEY", private_key);
+ intent.putExtra("KEY", public_key);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "could not store key pair: " + e);
+ }
+ return false;
+ }
+
+ /**
+ * Adds a cryptographic file (User certificate, a CA certificate or
+ * PKCS#12 keychain) through the system's CertInstaller activity.
+ *
+ * @param context: current application context.
+ * @param cert_type: cryptographic file type. E.g. CertificateMimeType.X509_USER_CERT
+ * @param data: certificate/keychain data bytes.
+ * @return true on success, false on failure.
+ *
+ * Note that failure only indicates that the function couldn't launch the
+ * CertInstaller activity, not that the certificate/keychain was properly
+ * installed to the keystore.
+ */
+ @CalledByNative
+ static public boolean storeCertificate(Context context, int cert_type, byte[] data) {
+ try {
+ Intent intent = KeyChain.createInstallIntent();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ switch (cert_type) {
+ case CertificateMimeType.X509_USER_CERT:
+ case CertificateMimeType.X509_CA_CERT:
+ intent.putExtra(KeyChain.EXTRA_CERTIFICATE, data);
+ break;
+
+ case CertificateMimeType.PKCS12_ARCHIVE:
+ intent.putExtra(KeyChain.EXTRA_PKCS12, data);
+ break;
+
+ default:
+ Log.w(TAG, "invalid certificate type: " + cert_type);
+ return false;
+ }
+ context.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "could not store crypto file: " + e);
+ }
+ return false;
+ }
+
+ /**
+ * @return the mime type (if any) that is associated with the file
+ * extension. Returns null if no corresponding mime type exists.
+ */
+ @CalledByNative
+ static public String getMimeTypeFromExtension(String extension) {
+ return URLConnection.guessContentTypeFromName("foo." + extension);
+ }
+
+ /**
+ * @return true if it can determine that only loopback addresses are
+ * configured. i.e. if only 127.0.0.1 and ::1 are routable. Also
+ * returns false if it cannot determine this.
+ */
+ @CalledByNative
+ static public boolean haveOnlyLoopbackAddresses() {
+ Enumeration<NetworkInterface> list = null;
+ try {
+ list = NetworkInterface.getNetworkInterfaces();
+ if (list == null) return false;
+ } catch (Exception e) {
+ Log.w(TAG, "could not get network interfaces: " + e);
+ return false;
+ }
+
+ while (list.hasMoreElements()) {
+ NetworkInterface netIf = list.nextElement();
+ try {
+ if (netIf.isUp() && !netIf.isLoopback()) return false;
+ } catch (SocketException e) {
+ continue;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return the network interfaces list (if any) string. The items in
+ * the list string are delimited by a semicolon ";", each item
+ * is a network interface name and address pair and formatted
+ * as "name,address". e.g.
+ * eth0,10.0.0.2;eth0,fe80::5054:ff:fe12:3456
+ * represents a network list string which containts two items.
+ */
+ @CalledByNative
+ static public String getNetworkList() {
+ Enumeration<NetworkInterface> list = null;
+ try {
+ list = NetworkInterface.getNetworkInterfaces();
+ if (list == null) return "";
+ } catch (SocketException e) {
+ Log.w(TAG, "Unable to get network interfaces: " + e);
+ return "";
+ }
+
+ StringBuilder result = new StringBuilder();
+ while (list.hasMoreElements()) {
+ NetworkInterface netIf = list.nextElement();
+ try {
+ // Skip loopback interfaces, and ones which are down.
+ if (!netIf.isUp() || netIf.isLoopback())
+ continue;
+ Enumeration<InetAddress> addressList = netIf.getInetAddresses();
+ while (addressList.hasMoreElements()) {
+ InetAddress address = addressList.nextElement();
+ // Skip loopback addresses configured on non-loopback interfaces.
+ if (address.isLoopbackAddress())
+ continue;
+ StringBuilder addressString = new StringBuilder();
+ addressString.append(netIf.getName());
+ addressString.append(",");
+
+ String ipAddress = address.getHostAddress();
+ if (address instanceof Inet6Address && ipAddress.contains("%")) {
+ ipAddress = ipAddress.substring(0, ipAddress.lastIndexOf("%"));
+ }
+ addressString.append(ipAddress);
+
+ if (result.length() != 0)
+ result.append(";");
+ result.append(addressString.toString());
+ }
+ } catch (SocketException e) {
+ continue;
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Validate the server's certificate chain is trusted.
+ *
+ * @param certChain The ASN.1 DER encoded bytes for certificates.
+ * @param authType The key exchange algorithm name (e.g. RSA)
+ * @return Android certificate verification result code.
+ */
+ @CalledByNative
+ public static int verifyServerCertificates(byte[][] certChain, String authType) {
+ try {
+ return X509Util.verifyServerCertificates(certChain, authType);
+ } catch (KeyStoreException e) {
+ return CertVerifyResultAndroid.VERIFY_FAILED;
+ } catch (NoSuchAlgorithmException e) {
+ return CertVerifyResultAndroid.VERIFY_FAILED;
+ }
+ }
+
+ /**
+ * Adds a test root certificate to the local trust store.
+ * @param rootCert DER encoded bytes of the certificate.
+ */
+ @CalledByNativeUnchecked
+ public static void addTestRootCertificate(byte[] rootCert) throws CertificateException,
+ KeyStoreException, NoSuchAlgorithmException {
+ X509Util.addTestRootCertificate(rootCert);
+ }
+
+ /**
+ * Removes all test root certificates added by |addTestRootCertificate| calls from the local
+ * trust store.
+ */
+ @CalledByNativeUnchecked
+ public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
+ CertificateException, KeyStoreException {
+ X509Util.clearTestRootCertificates();
+ }
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/GURLUtils.java b/chromium/net/android/java/src/org/chromium/net/GURLUtils.java
new file mode 100644
index 00000000000..719ddeabb23
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/GURLUtils.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import org.chromium.base.JNINamespace;
+
+/**
+ * Class to access the GURL library from java.
+ */
+@JNINamespace("net")
+public final class GURLUtils {
+
+ /**
+ * Get the origin of an url: Ex getOrigin("http://www.example.com:8080/index.html?bar=foo")
+ * would return "http://www.example.com:8080". It will return an empty string for an
+ * invalid url.
+ *
+ * @return The origin of the url
+ */
+ public static String getOrigin(String url) {
+ return nativeGetOrigin(url);
+ }
+
+ /**
+ * Get the scheme of the url (e.g. http, https, file). The returned string
+ * contains everything before the "://".
+ *
+ * @return The scheme of the url.
+ */
+ public static String getScheme(String url) {
+ return nativeGetScheme(url);
+ }
+
+ private static native String nativeGetOrigin(String url);
+ private static native String nativeGetScheme(String url);
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java b/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
new file mode 100644
index 00000000000..a5de98313c2
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
@@ -0,0 +1,224 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import android.content.Context;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.NativeClassQualifiedName;
+import org.chromium.base.ObserverList;
+
+import java.util.ArrayList;
+
+/**
+ * Triggers updates to the underlying network state in Chrome.
+ *
+ * By default, connectivity is assumed and changes must pushed from the embedder via the
+ * forceConnectivityState function.
+ * Embedders may choose to have this class auto-detect changes in network connectivity by invoking
+ * the setAutoDetectConnectivityState function.
+ *
+ * WARNING: This class is not thread-safe.
+ */
+@JNINamespace("net")
+public class NetworkChangeNotifier {
+ /**
+ * Alerted when the connection type of the network changes.
+ * The alert is fired on the UI thread.
+ */
+ public interface ConnectionTypeObserver {
+ public void onConnectionTypeChanged(int connectionType);
+ }
+
+ // These constants must always match the ones in network_change_notifier.h.
+ public static final int CONNECTION_UNKNOWN = 0;
+ public static final int CONNECTION_ETHERNET = 1;
+ public static final int CONNECTION_WIFI = 2;
+ public static final int CONNECTION_2G = 3;
+ public static final int CONNECTION_3G = 4;
+ public static final int CONNECTION_4G = 5;
+ public static final int CONNECTION_NONE = 6;
+
+ private final Context mContext;
+ private final ArrayList<Integer> mNativeChangeNotifiers;
+ private final ObserverList<ConnectionTypeObserver> mConnectionTypeObservers;
+ private NetworkChangeNotifierAutoDetect mAutoDetector;
+ private int mCurrentConnectionType = CONNECTION_UNKNOWN;
+
+ private static NetworkChangeNotifier sInstance;
+
+ private NetworkChangeNotifier(Context context) {
+ mContext = context;
+ mNativeChangeNotifiers = new ArrayList<Integer>();
+ mConnectionTypeObservers = new ObserverList<ConnectionTypeObserver>();
+ }
+
+ /**
+ * Initializes the singleton once.
+ */
+ @CalledByNative
+ public static NetworkChangeNotifier init(Context context) {
+ if (sInstance == null) {
+ sInstance = new NetworkChangeNotifier(context);
+ }
+ return sInstance;
+ }
+
+ public static boolean isInitialized() {
+ return sInstance != null;
+ }
+
+ static void resetInstanceForTests(Context context) {
+ sInstance = new NetworkChangeNotifier(context);
+ }
+
+ @CalledByNative
+ public int getCurrentConnectionType() {
+ return mCurrentConnectionType;
+ }
+
+ /**
+ * Adds a native-side observer.
+ */
+ @CalledByNative
+ public void addNativeObserver(int nativeChangeNotifier) {
+ mNativeChangeNotifiers.add(nativeChangeNotifier);
+ }
+
+ /**
+ * Removes a native-side observer.
+ */
+ @CalledByNative
+ public void removeNativeObserver(int nativeChangeNotifier) {
+ // Please keep the cast performing the boxing below. It ensures that the right method
+ // overload is used. ArrayList<T> has both remove(int index) and remove(T element).
+ mNativeChangeNotifiers.remove((Integer) nativeChangeNotifier);
+ }
+
+ /**
+ * Returns the singleton instance.
+ */
+ public static NetworkChangeNotifier getInstance() {
+ assert sInstance != null;
+ return sInstance;
+ }
+
+ /**
+ * Enables auto detection of the current network state based on notifications from the system.
+ * Note that passing true here requires the embedding app have the platform ACCESS_NETWORK_STATE
+ * permission.
+ *
+ * @param shouldAutoDetect true if the NetworkChangeNotifier should listen for system changes in
+ * network connectivity.
+ */
+ public static void setAutoDetectConnectivityState(boolean shouldAutoDetect) {
+ getInstance().setAutoDetectConnectivityStateInternal(shouldAutoDetect);
+ }
+
+ private void destroyAutoDetector() {
+ if (mAutoDetector != null) {
+ mAutoDetector.destroy();
+ mAutoDetector = null;
+ }
+ }
+
+ private void setAutoDetectConnectivityStateInternal(boolean shouldAutoDetect) {
+ if (shouldAutoDetect) {
+ if (mAutoDetector == null) {
+ mAutoDetector = new NetworkChangeNotifierAutoDetect(
+ new NetworkChangeNotifierAutoDetect.Observer() {
+ @Override
+ public void onConnectionTypeChanged(int newConnectionType) {
+ updateCurrentConnectionType(newConnectionType);
+ }
+ },
+ mContext);
+ mCurrentConnectionType = mAutoDetector.getCurrentConnectionType();
+ }
+ } else {
+ destroyAutoDetector();
+ }
+ }
+
+ /**
+ * Updates the perceived network state when not auto-detecting changes to connectivity.
+ *
+ * @param networkAvailable True if the NetworkChangeNotifier should perceive a "connected"
+ * state, false implies "disconnected".
+ */
+ @CalledByNative
+ public static void forceConnectivityState(boolean networkAvailable) {
+ setAutoDetectConnectivityState(false);
+ getInstance().forceConnectivityStateInternal(networkAvailable);
+ }
+
+ private void forceConnectivityStateInternal(boolean forceOnline) {
+ boolean connectionCurrentlyExists = mCurrentConnectionType != CONNECTION_NONE;
+ if (connectionCurrentlyExists != forceOnline) {
+ updateCurrentConnectionType(forceOnline ? CONNECTION_UNKNOWN : CONNECTION_NONE);
+ }
+ }
+
+ private void updateCurrentConnectionType(int newConnectionType) {
+ mCurrentConnectionType = newConnectionType;
+ notifyObserversOfConnectionTypeChange(newConnectionType);
+ }
+
+ /**
+ * Alerts all observers of a connection change.
+ */
+ void notifyObserversOfConnectionTypeChange(int newConnectionType) {
+ for (Integer nativeChangeNotifier : mNativeChangeNotifiers) {
+ nativeNotifyConnectionTypeChanged(nativeChangeNotifier, newConnectionType);
+ }
+ for (ConnectionTypeObserver observer : mConnectionTypeObservers) {
+ observer.onConnectionTypeChanged(newConnectionType);
+ }
+ }
+
+ /**
+ * Adds an observer for any connection type changes.
+ */
+ public static void addConnectionTypeObserver(ConnectionTypeObserver observer) {
+ getInstance().addConnectionTypeObserverInternal(observer);
+ }
+
+ private void addConnectionTypeObserverInternal(ConnectionTypeObserver observer) {
+ if (!mConnectionTypeObservers.hasObserver(observer)) {
+ mConnectionTypeObservers.addObserver(observer);
+ }
+ }
+
+ /**
+ * Removes an observer for any connection type changes.
+ */
+ public static void removeConnectionTypeObserver(ConnectionTypeObserver observer) {
+ getInstance().removeConnectionTypeObserverInternal(observer);
+ }
+
+ private void removeConnectionTypeObserverInternal(ConnectionTypeObserver observer) {
+ mConnectionTypeObservers.removeObserver(observer);
+ }
+
+ @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
+ private native void nativeNotifyConnectionTypeChanged(int nativePtr, int newConnectionType);
+
+ @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
+ private native int nativeGetConnectionType(int nativePtr);
+
+ // For testing only.
+ public static NetworkChangeNotifierAutoDetect getAutoDetectorForTest() {
+ return getInstance().mAutoDetector;
+ }
+
+ /**
+ * Checks if there currently is connectivity.
+ */
+ public static boolean isOnline() {
+ int connectionType = getInstance().getCurrentConnectionType();
+ return connectionType != CONNECTION_UNKNOWN && connectionType != CONNECTION_NONE;
+ }
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java b/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
new file mode 100644
index 00000000000..038cb3124ac
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
@@ -0,0 +1,196 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import org.chromium.base.ActivityStatus;
+
+/**
+ * Used by the NetworkChangeNotifier to listens to platform changes in connectivity.
+ * Note that use of this class requires that the app have the platform
+ * ACCESS_NETWORK_STATE permission.
+ */
+public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver
+ implements ActivityStatus.StateListener {
+
+ /** Queries the ConnectivityManager for information about the current connection. */
+ static class ConnectivityManagerDelegate {
+ private final ConnectivityManager mConnectivityManager;
+
+ ConnectivityManagerDelegate(Context context) {
+ mConnectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ // For testing.
+ ConnectivityManagerDelegate() {
+ // All the methods below should be overridden.
+ mConnectivityManager = null;
+ }
+
+ boolean activeNetworkExists() {
+ return mConnectivityManager.getActiveNetworkInfo() != null;
+ }
+
+ boolean isConnected() {
+ return mConnectivityManager.getActiveNetworkInfo().isConnected();
+ }
+
+ int getNetworkType() {
+ return mConnectivityManager.getActiveNetworkInfo().getType();
+ }
+
+ int getNetworkSubtype() {
+ return mConnectivityManager.getActiveNetworkInfo().getSubtype();
+ }
+ }
+
+ private static final String TAG = "NetworkChangeNotifierAutoDetect";
+
+ private final NetworkConnectivityIntentFilter mIntentFilter =
+ new NetworkConnectivityIntentFilter();
+
+ private final Observer mObserver;
+
+ private final Context mContext;
+ private ConnectivityManagerDelegate mConnectivityManagerDelegate;
+ private boolean mRegistered;
+ private int mConnectionType;
+
+ /**
+ * Observer notified on the UI thread whenever a new connection type was detected.
+ */
+ public static interface Observer {
+ public void onConnectionTypeChanged(int newConnectionType);
+ }
+
+ public NetworkChangeNotifierAutoDetect(Observer observer, Context context) {
+ mObserver = observer;
+ mContext = context.getApplicationContext();
+ mConnectivityManagerDelegate = new ConnectivityManagerDelegate(context);
+ mConnectionType = getCurrentConnectionType();
+ ActivityStatus.registerStateListener(this);
+ }
+
+ /**
+ * Allows overriding the ConnectivityManagerDelegate for tests.
+ */
+ void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) {
+ mConnectivityManagerDelegate = delegate;
+ }
+
+ public void destroy() {
+ unregisterReceiver();
+ }
+
+ /**
+ * Register a BroadcastReceiver in the given context.
+ */
+ private void registerReceiver() {
+ if (!mRegistered) {
+ mRegistered = true;
+ mContext.registerReceiver(this, mIntentFilter);
+ }
+ }
+
+ /**
+ * Unregister the BroadcastReceiver in the given context.
+ */
+ private void unregisterReceiver() {
+ if (mRegistered) {
+ mRegistered = false;
+ mContext.unregisterReceiver(this);
+ }
+ }
+
+ public int getCurrentConnectionType() {
+ // Track exactly what type of connection we have.
+ if (!mConnectivityManagerDelegate.activeNetworkExists() ||
+ !mConnectivityManagerDelegate.isConnected()) {
+ return NetworkChangeNotifier.CONNECTION_NONE;
+ }
+
+ switch (mConnectivityManagerDelegate.getNetworkType()) {
+ case ConnectivityManager.TYPE_ETHERNET:
+ return NetworkChangeNotifier.CONNECTION_ETHERNET;
+ case ConnectivityManager.TYPE_WIFI:
+ return NetworkChangeNotifier.CONNECTION_WIFI;
+ case ConnectivityManager.TYPE_WIMAX:
+ return NetworkChangeNotifier.CONNECTION_4G;
+ case ConnectivityManager.TYPE_MOBILE:
+ // Use information from TelephonyManager to classify the connection.
+ switch (mConnectivityManagerDelegate.getNetworkSubtype()) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return NetworkChangeNotifier.CONNECTION_2G;
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return NetworkChangeNotifier.CONNECTION_3G;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return NetworkChangeNotifier.CONNECTION_4G;
+ default:
+ return NetworkChangeNotifier.CONNECTION_UNKNOWN;
+ }
+ default:
+ return NetworkChangeNotifier.CONNECTION_UNKNOWN;
+ }
+ }
+
+ // BroadcastReceiver
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ connectionTypeChanged();
+ }
+
+ // ActivityStatus.StateListener
+ @Override
+ public void onActivityStateChange(int state) {
+ if (state == ActivityStatus.RESUMED) {
+ // Note that this also covers the case where the main activity is created. The CREATED
+ // event is always followed by the RESUMED event. This is a temporary "hack" until
+ // http://crbug.com/176837 is fixed. The CREATED event can't be used reliably for now
+ // since its notification is deferred. This means that it can immediately follow a
+ // DESTROYED/STOPPED/... event which is problematic.
+ // TODO(pliard): fix http://crbug.com/176837.
+ connectionTypeChanged();
+ registerReceiver();
+ } else if (state == ActivityStatus.PAUSED) {
+ unregisterReceiver();
+ }
+ }
+
+ private void connectionTypeChanged() {
+ int newConnectionType = getCurrentConnectionType();
+ if (newConnectionType == mConnectionType) return;
+
+ mConnectionType = newConnectionType;
+ Log.d(TAG, "Network connectivity changed, type is: " + mConnectionType);
+ mObserver.onConnectionTypeChanged(newConnectionType);
+ }
+
+ private static class NetworkConnectivityIntentFilter extends IntentFilter {
+ NetworkConnectivityIntentFilter() {
+ addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ }
+ }
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/ProxyChangeListener.java b/chromium/net/android/java/src/org/chromium/net/ProxyChangeListener.java
new file mode 100644
index 00000000000..9c59bcccf6d
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/ProxyChangeListener.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Proxy;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.NativeClassQualifiedName;
+
+// This class partners with native ProxyConfigServiceAndroid to listen for
+// proxy change notifications from Android.
+@JNINamespace("net")
+public class ProxyChangeListener {
+ private static final String TAG = "ProxyChangeListener";
+ private static boolean sEnabled = true;
+
+ private int mNativePtr;
+ private Context mContext;
+ private ProxyReceiver mProxyReceiver;
+ private Delegate mDelegate;
+
+ public interface Delegate {
+ public void proxySettingsChanged();
+ }
+
+ private ProxyChangeListener(Context context) {
+ mContext = context;
+ }
+
+ public static void setEnabled(boolean enabled) {
+ sEnabled = enabled;
+ }
+
+ public void setDelegateForTesting(Delegate delegate) {
+ mDelegate = delegate;
+ }
+
+ @CalledByNative
+ static public ProxyChangeListener create(Context context) {
+ return new ProxyChangeListener(context);
+ }
+
+ @CalledByNative
+ static public String getProperty(String property) {
+ return System.getProperty(property);
+ }
+
+ @CalledByNative
+ public void start(int nativePtr) {
+ assert mNativePtr == 0;
+ mNativePtr = nativePtr;
+ registerReceiver();
+ }
+
+ @CalledByNative
+ public void stop() {
+ mNativePtr = 0;
+ unregisterReceiver();
+ }
+
+ private class ProxyReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
+ proxySettingsChanged();
+ }
+ }
+ }
+
+ private void proxySettingsChanged() {
+ if (!sEnabled) {
+ return;
+ }
+ if (mDelegate != null) {
+ mDelegate.proxySettingsChanged();
+ }
+ if (mNativePtr == 0) {
+ return;
+ }
+ // Note that this code currently runs on a MESSAGE_LOOP_UI thread, but
+ // the C++ code must run the callbacks on the network thread.
+ nativeProxySettingsChanged(mNativePtr);
+ }
+
+ private void registerReceiver() {
+ if (mProxyReceiver != null) {
+ return;
+ }
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Proxy.PROXY_CHANGE_ACTION);
+ mProxyReceiver = new ProxyReceiver();
+ mContext.getApplicationContext().registerReceiver(mProxyReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (mProxyReceiver == null) {
+ return;
+ }
+ mContext.unregisterReceiver(mProxyReceiver);
+ mProxyReceiver = null;
+ }
+
+ /**
+ * See net/proxy/proxy_config_service_android.cc
+ */
+ @NativeClassQualifiedName("ProxyConfigServiceAndroid::JNIDelegate")
+ private native void nativeProxySettingsChanged(int nativePtr);
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/X509Util.java b/chromium/net/android/java/src/org/chromium/net/X509Util.java
new file mode 100644
index 00000000000..30007caab17
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/X509Util.java
@@ -0,0 +1,233 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import android.util.Log;
+
+import org.chromium.net.CertVerifyResultAndroid;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+public class X509Util {
+
+ private static final String TAG = "X509Util";
+
+ private static CertificateFactory sCertificateFactory;
+
+ private static final String OID_TLS_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
+ private static final String OID_ANY_EKU = "2.5.29.37.0";
+ // Server-Gated Cryptography (necessary to support a few legacy issuers):
+ // Netscape:
+ private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1";
+ // Microsoft:
+ private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3";
+
+ /**
+ * Trust manager backed up by the read-only system certificate store.
+ */
+ private static X509TrustManager sDefaultTrustManager;
+
+ /**
+ * Trust manager backed up by a custom certificate store. We need such manager to plant test
+ * root CA to the trust store in testing.
+ */
+ private static X509TrustManager sTestTrustManager;
+ private static KeyStore sTestKeyStore;
+
+ /**
+ * Lock object used to synchronize all calls that modify or depend on the trust managers.
+ */
+ private static final Object sLock = new Object();
+
+ /**
+ * Ensures that the trust managers and certificate factory are initialized.
+ */
+ private static void ensureInitialized() throws CertificateException,
+ KeyStoreException, NoSuchAlgorithmException {
+ synchronized(sLock) {
+ if (sCertificateFactory == null) {
+ sCertificateFactory = CertificateFactory.getInstance("X.509");
+ }
+ if (sDefaultTrustManager == null) {
+ sDefaultTrustManager = X509Util.createTrustManager(null);
+ }
+ if (sTestKeyStore == null) {
+ sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ try {
+ sTestKeyStore.load(null);
+ } catch(IOException e) {} // No IO operation is attempted.
+ }
+ if (sTestTrustManager == null) {
+ sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
+ }
+ }
+ }
+
+ /**
+ * Creates a X509TrustManager backed up by the given key store. When null is passed as a key
+ * store, system default trust store is used.
+ * @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
+ */
+ private static X509TrustManager createTrustManager(KeyStore keyStore) throws KeyStoreException,
+ NoSuchAlgorithmException {
+ String algorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+ tmf.init(keyStore);
+
+ for (TrustManager tm : tmf.getTrustManagers()) {
+ if (tm instanceof X509TrustManager) {
+ return (X509TrustManager) tm;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * After each modification of test key store, trust manager has to be generated again.
+ */
+ private static void reloadTestTrustManager() throws KeyStoreException,
+ NoSuchAlgorithmException {
+ sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
+ }
+
+ /**
+ * Convert a DER encoded certificate to an X509Certificate.
+ */
+ public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
+ CertificateException, KeyStoreException, NoSuchAlgorithmException {
+ ensureInitialized();
+ return (X509Certificate) sCertificateFactory.generateCertificate(
+ new ByteArrayInputStream(derBytes));
+ }
+
+ public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
+ KeyStoreException, NoSuchAlgorithmException {
+ ensureInitialized();
+ X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
+ synchronized (sLock) {
+ sTestKeyStore.setCertificateEntry(
+ "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
+ reloadTestTrustManager();
+ }
+ }
+
+ public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
+ CertificateException, KeyStoreException {
+ ensureInitialized();
+ synchronized (sLock) {
+ try {
+ sTestKeyStore.load(null);
+ reloadTestTrustManager();
+ } catch (IOException e) {} // No IO operation is attempted.
+ }
+ }
+
+ /**
+ * If an EKU extension is present in the end-entity certificate, it MUST contain either the
+ * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
+ *
+ * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid
+ * OIDs for web server certificates.
+ *
+ * TODO(palmer): This can be removed after the equivalent change is made to the Android default
+ * TrustManager and that change is shipped to a large majority of Android users.
+ */
+ static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
+ List<String> ekuOids;
+ try {
+ ekuOids = certificate.getExtendedKeyUsage();
+ } catch (NullPointerException e) {
+ // getExtendedKeyUsage() can crash due to an Android platform bug. This probably
+ // happens when the EKU extension data is malformed so return false here.
+ // See http://crbug.com/233610
+ return false;
+ }
+ if (ekuOids == null)
+ return true;
+
+ for (String ekuOid : ekuOids) {
+ if (ekuOid.equals(OID_TLS_SERVER_AUTH) ||
+ ekuOid.equals(OID_ANY_EKU) ||
+ ekuOid.equals(OID_SERVER_GATED_NETSCAPE) ||
+ ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static int verifyServerCertificates(byte[][] certChain, String authType)
+ throws KeyStoreException, NoSuchAlgorithmException {
+ if (certChain == null || certChain.length == 0 || certChain[0] == null) {
+ throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
+ "chain passed as |certChain|. |certChain|=" + certChain);
+ }
+
+ try {
+ ensureInitialized();
+ } catch (CertificateException e) {
+ return CertVerifyResultAndroid.VERIFY_FAILED;
+ }
+
+ X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
+ try {
+ for (int i = 0; i < certChain.length; ++i) {
+ serverCertificates[i] = createCertificateFromBytes(certChain[i]);
+ }
+ } catch (CertificateException e) {
+ return CertVerifyResultAndroid.VERIFY_UNABLE_TO_PARSE;
+ }
+
+ // Expired and not yet valid certificates would be rejected by the trust managers, but the
+ // trust managers report all certificate errors using the general CertificateException. In
+ // order to get more granular error information, cert validity time range is being checked
+ // separately.
+ try {
+ serverCertificates[0].checkValidity();
+ if (!verifyKeyUsage(serverCertificates[0]))
+ return CertVerifyResultAndroid.VERIFY_INCORRECT_KEY_USAGE;
+ } catch (CertificateExpiredException e) {
+ return CertVerifyResultAndroid.VERIFY_EXPIRED;
+ } catch (CertificateNotYetValidException e) {
+ return CertVerifyResultAndroid.VERIFY_NOT_YET_VALID;
+ } catch (CertificateException e) {
+ return CertVerifyResultAndroid.VERIFY_FAILED;
+ }
+
+ synchronized (sLock) {
+ try {
+ sDefaultTrustManager.checkServerTrusted(serverCertificates, authType);
+ return CertVerifyResultAndroid.VERIFY_OK;
+ } catch (CertificateException eDefaultManager) {
+ try {
+ sTestTrustManager.checkServerTrusted(serverCertificates, authType);
+ return CertVerifyResultAndroid.VERIFY_OK;
+ } catch (CertificateException eTestManager) {
+ // Neither of the trust managers confirms the validity of the certificate chain,
+ // log the error message returned by the system trust manager.
+ Log.i(TAG, "Failed to validate the certificate chain, error: " +
+ eDefaultManager.getMessage());
+ return CertVerifyResultAndroid.VERIFY_NO_TRUSTED_ROOT;
+ }
+ }
+ }
+ }
+}