diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/net/android/java/src | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/net/android/java/src')
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; + } + } + } + } +} |