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 | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/net/android')
39 files changed, 5115 insertions, 0 deletions
diff --git a/chromium/net/android/OWNERS b/chromium/net/android/OWNERS new file mode 100644 index 00000000000..4bcb60f934c --- /dev/null +++ b/chromium/net/android/OWNERS @@ -0,0 +1,3 @@ +digit@chromium.org +pliard@chromium.org +yfriedman@chromium.org diff --git a/chromium/net/android/cert_verify_result_android.h b/chromium/net/android/cert_verify_result_android.h new file mode 100644 index 00000000000..ac18c218cfd --- /dev/null +++ b/chromium/net/android/cert_verify_result_android.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef NET_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_ +#define NET_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_ + +#include "base/basictypes.h" +#include "base/platform_file.h" +#include "net/base/net_export.h" + +namespace net { + +namespace android { + +enum CertVerifyResultAndroid { +#define CERT_VERIFY_RESULT_ANDROID(label, value) VERIFY_ ## label = value, +#include "net/android/cert_verify_result_android_list.h" +#undef CERT_VERIFY_RESULT_ANDROID +}; + +} // namespace android + +} // namespace net + +#endif // NET_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_ diff --git a/chromium/net/android/cert_verify_result_android_list.h b/chromium/net/android/cert_verify_result_android_list.h new file mode 100644 index 00000000000..a6d882f1e03 --- /dev/null +++ b/chromium/net/android/cert_verify_result_android_list.h @@ -0,0 +1,31 @@ +// 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. + +// This file intentionally does not have header guards, it's included +// inside a macro to generate enum values. + +// This file contains the list of certificate verification results returned +// from Java side to the C++ side. + +// Certificate is trusted. +CERT_VERIFY_RESULT_ANDROID(OK, 0) + +// Certificate verification could not be conducted. +CERT_VERIFY_RESULT_ANDROID(FAILED, -1) + +// Certificate is not trusted due to non-trusted root of the certificate chain. +CERT_VERIFY_RESULT_ANDROID(NO_TRUSTED_ROOT, -2) + +// Certificate is not trusted because it has expired. +CERT_VERIFY_RESULT_ANDROID(EXPIRED, -3) + +// Certificate is not trusted because it is not valid yet. +CERT_VERIFY_RESULT_ANDROID(NOT_YET_VALID, -4) + +// Certificate is not trusted because it could not be parsed. +CERT_VERIFY_RESULT_ANDROID(UNABLE_TO_PARSE, -5) + +// Certificate is not trusted because it has an extendedKeyUsage field, but +// its value is not correct for a web server. +CERT_VERIFY_RESULT_ANDROID(INCORRECT_KEY_USAGE, -6) diff --git a/chromium/net/android/gurl_utils.cc b/chromium/net/android/gurl_utils.cc new file mode 100644 index 00000000000..c8cb1e4e998 --- /dev/null +++ b/chromium/net/android/gurl_utils.cc @@ -0,0 +1,31 @@ +// 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. + +#include "net/android/gurl_utils.h" + +#include "base/android/jni_string.h" +#include "jni/GURLUtils_jni.h" +#include "url/gurl.h" + +namespace net { + +jstring GetOrigin(JNIEnv* env, jclass clazz, jstring url) { + GURL host(base::android::ConvertJavaStringToUTF16(env, url)); + + return base::android::ConvertUTF8ToJavaString(env, + host.GetOrigin().spec()).Release(); +} + +jstring GetScheme(JNIEnv* env, jclass clazz, jstring url) { + GURL host(base::android::ConvertJavaStringToUTF16(env, url)); + + return base::android::ConvertUTF8ToJavaString(env, + host.scheme()).Release(); +} + +bool RegisterGURLUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // net namespace diff --git a/chromium/net/android/gurl_utils.h b/chromium/net/android/gurl_utils.h new file mode 100644 index 00000000000..aeeecf7b4f4 --- /dev/null +++ b/chromium/net/android/gurl_utils.h @@ -0,0 +1,16 @@ +// 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. + +#ifndef NET_ANDROID_GURL_UTILS_H_ +#define NET_ANDROID_GURL_UTILS_H_ + +#include <jni.h> + +namespace net { + +bool RegisterGURLUtils(JNIEnv* env); + +} // net namespace + +#endif // NET_ANDROID_GURL_UTILS_H_ diff --git a/chromium/net/android/java/CertVerifyResultAndroid.template b/chromium/net/android/java/CertVerifyResultAndroid.template new file mode 100644 index 00000000000..b19e937fcb9 --- /dev/null +++ b/chromium/net/android/java/CertVerifyResultAndroid.template @@ -0,0 +1,10 @@ +// 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; + +public class CertVerifyResultAndroid { +#define CERT_VERIFY_RESULT_ANDROID(name, value) public static final int VERIFY_##name = value; +#include "net/android/cert_verify_result_android_list.h" +} diff --git a/chromium/net/android/java/CertificateMimeType.template b/chromium/net/android/java/CertificateMimeType.template new file mode 100644 index 00000000000..5a21171e88b --- /dev/null +++ b/chromium/net/android/java/CertificateMimeType.template @@ -0,0 +1,11 @@ +// 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; + +public class CertificateMimeType { +#define CERTIFICATE_MIME_TYPE(name, value) public static final int name = value; +#include "net/base/mime_util_certificate_type_list.h" +#undef CERTIFICATE_MIME_TYPE +} diff --git a/chromium/net/android/java/NetError.template b/chromium/net/android/java/NetError.template new file mode 100644 index 00000000000..f6c16617b3b --- /dev/null +++ b/chromium/net/android/java/NetError.template @@ -0,0 +1,10 @@ +// 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; + +public class NetError { +#define NET_ERROR(name, value) public static final int ERR_##name = value; +#include "net/base/net_error_list.h" +} diff --git a/chromium/net/android/java/PrivateKeyType.template b/chromium/net/android/java/PrivateKeyType.template new file mode 100644 index 00000000000..aa7f76f8881 --- /dev/null +++ b/chromium/net/android/java/PrivateKeyType.template @@ -0,0 +1,10 @@ +// 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; + +public class PrivateKeyType { +#define DEFINE_PRIVATE_KEY_TYPE(name,value) public static final int name = value; +#include "net/android/private_key_type_list.h" +} 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; + } + } + } + } +} diff --git a/chromium/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java b/chromium/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java new file mode 100644 index 00000000000..460dc50cabb --- /dev/null +++ b/chromium/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java @@ -0,0 +1,67 @@ +// 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.os.Build; +import android.util.Log; + +import java.security.PrivateKey; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.KeyFactory; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.KeyStoreException; +import java.security.spec.InvalidKeySpecException; +import java.security.NoSuchAlgorithmException; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.net.PrivateKeyType; + +@JNINamespace("net::android") +public class AndroidKeyStoreTestUtil { + + private static final String TAG = "AndroidKeyStoreTestUtil"; + + /** + * Called from native code to create a PrivateKey object from its + * encoded PKCS#8 representation. + * @param type The key type, accoding to PrivateKeyType. + * @return new PrivateKey handle, or null in case of error. + */ + @CalledByNative + public static PrivateKey createPrivateKeyFromPKCS8(int type, + byte[] encoded_key) { + String algorithm = null; + switch (type) { + case PrivateKeyType.RSA: + algorithm = "RSA"; + break; + case PrivateKeyType.DSA: + algorithm = "DSA"; + break; + case PrivateKeyType.ECDSA: + algorithm = "EC"; + break; + default: + return null; + } + + try { + KeyFactory factory = KeyFactory.getInstance(algorithm); + KeySpec ks = new PKCS8EncodedKeySpec(encoded_key); + PrivateKey key = factory.generatePrivate(ks); + return key; + + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "Could not create " + algorithm + " factory instance!"); + return null; + } catch (InvalidKeySpecException e) { + Log.e(TAG, "Could not load " + algorithm + " private key from bytes!"); + return null; + } + } +} diff --git a/chromium/net/android/javatests/src/org/chromium/net/AndroidProxySelectorTest.java b/chromium/net/android/javatests/src/org/chromium/net/AndroidProxySelectorTest.java new file mode 100644 index 00000000000..c705f69be37 --- /dev/null +++ b/chromium/net/android/javatests/src/org/chromium/net/AndroidProxySelectorTest.java @@ -0,0 +1,295 @@ +// 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. + +/** + * Test suite for Android's default ProxySelector implementation. The purpose of these tests + * is to check that the behaviour of the ProxySelector implementation matches what we have + * implemented in net/proxy/proxy_config_service_android.cc. + * + * IMPORTANT: These test cases are generated from net/android/tools/proxy_test_cases.py, so if any + * of these tests fail, please be sure to edit that file and regenerate the test cases here and also + * in net/proxy/proxy_config_service_android_unittests.cc if required. + */ + +package org.chromium.net; + +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Properties; + +import org.chromium.base.test.util.Feature; + +public class AndroidProxySelectorTest extends InstrumentationTestCase { + Properties mProperties; + + public AndroidProxySelectorTest() { + // Start with a clean slate in case there is a system proxy configured. + mProperties = new Properties(); + } + + @Override + public void setUp() { + System.setProperties(mProperties); + } + + static String toString(Proxy proxy) { + if (proxy == Proxy.NO_PROXY) + return "DIRECT"; + // java.net.Proxy only knows about http and socks proxies. + Proxy.Type type = proxy.type(); + switch (type) { + case HTTP: return "PROXY " + proxy.address().toString(); + case SOCKS: return "SOCKS5 " + proxy.address().toString(); + case DIRECT: return "DIRECT"; + default: + // If a new proxy type is supported in future, add a case to match it. + fail("Unknown proxy type" + type); + return "unknown://"; + } + } + + static String toString(List<Proxy> proxies) { + StringBuilder builder = new StringBuilder(); + for (Proxy proxy : proxies) { + if (builder.length() > 0) + builder.append(';'); + builder.append(toString(proxy)); + } + return builder.toString(); + } + + static void checkMapping(String url, String expected) throws URISyntaxException { + URI uri = new URI(url); + List<Proxy> proxies = ProxySelector.getDefault().select(uri); + assertEquals("Mapping", expected, toString(proxies)); + } + + /** + * Test direct mapping when no proxy defined. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testNoProxy() throws Exception { + checkMapping("ftp://example.com/", "DIRECT"); + checkMapping("http://example.com/", "DIRECT"); + checkMapping("https://example.com/", "DIRECT"); + } + + /** + * Test http.proxyHost and http.proxyPort works. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpProxyHostAndPort() throws Exception { + System.setProperty("http.proxyHost", "httpproxy.com"); + System.setProperty("http.proxyPort", "8080"); + checkMapping("ftp://example.com/", "DIRECT"); + checkMapping("http://example.com/", "PROXY httpproxy.com:8080"); + checkMapping("https://example.com/", "DIRECT"); + } + + /** + * We should get the default port (80) for proxied hosts. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpProxyHostOnly() throws Exception { + System.setProperty("http.proxyHost", "httpproxy.com"); + checkMapping("ftp://example.com/", "DIRECT"); + checkMapping("http://example.com/", "PROXY httpproxy.com:80"); + checkMapping("https://example.com/", "DIRECT"); + } + + /** + * http.proxyPort only should not result in any hosts being proxied. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpProxyPortOnly() throws Exception { + System.setProperty("http.proxyPort", "8080"); + checkMapping("ftp://example.com/", "DIRECT"); + checkMapping("http://example.com/", "DIRECT"); + checkMapping("https://example.com/", "DIRECT"); + } + + /** + * Test that HTTP non proxy hosts are mapped correctly + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpNonProxyHosts1() throws Exception { + System.setProperty("http.nonProxyHosts", "slashdot.org"); + System.setProperty("http.proxyHost", "httpproxy.com"); + System.setProperty("http.proxyPort", "8080"); + checkMapping("http://example.com/", "PROXY httpproxy.com:8080"); + checkMapping("http://slashdot.org/", "DIRECT"); + } + + /** + * Test that | pattern works. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpNonProxyHosts2() throws Exception { + System.setProperty("http.nonProxyHosts", "slashdot.org|freecode.net"); + System.setProperty("http.proxyHost", "httpproxy.com"); + System.setProperty("http.proxyPort", "8080"); + checkMapping("http://example.com/", "PROXY httpproxy.com:8080"); + checkMapping("http://freecode.net/", "DIRECT"); + checkMapping("http://slashdot.org/", "DIRECT"); + } + + /** + * Test that * pattern works. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpNonProxyHosts3() throws Exception { + System.setProperty("http.nonProxyHosts", "*example.com"); + System.setProperty("http.proxyHost", "httpproxy.com"); + System.setProperty("http.proxyPort", "8080"); + checkMapping("http://example.com/", "DIRECT"); + checkMapping("http://slashdot.org/", "PROXY httpproxy.com:8080"); + checkMapping("http://www.example.com/", "DIRECT"); + } + + /** + * Test that FTP non proxy hosts are mapped correctly + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testFtpNonProxyHosts() throws Exception { + System.setProperty("ftp.nonProxyHosts", "slashdot.org"); + System.setProperty("ftp.proxyHost", "httpproxy.com"); + System.setProperty("ftp.proxyPort", "8080"); + checkMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + checkMapping("http://example.com/", "DIRECT"); + } + + /** + * Test ftp.proxyHost and ftp.proxyPort works. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testFtpProxyHostAndPort() throws Exception { + System.setProperty("ftp.proxyHost", "httpproxy.com"); + System.setProperty("ftp.proxyPort", "8080"); + checkMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + checkMapping("http://example.com/", "DIRECT"); + checkMapping("https://example.com/", "DIRECT"); + } + + /** + * Test ftp.proxyHost and default port. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testFtpProxyHostOnly() throws Exception { + System.setProperty("ftp.proxyHost", "httpproxy.com"); + checkMapping("ftp://example.com/", "PROXY httpproxy.com:80"); + checkMapping("http://example.com/", "DIRECT"); + checkMapping("https://example.com/", "DIRECT"); + } + + /** + * Test https.proxyHost and https.proxyPort works. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpsProxyHostAndPort() throws Exception { + System.setProperty("https.proxyHost", "httpproxy.com"); + System.setProperty("https.proxyPort", "8080"); + checkMapping("ftp://example.com/", "DIRECT"); + checkMapping("http://example.com/", "DIRECT"); + checkMapping("https://example.com/", "PROXY httpproxy.com:8080"); + } + + /** + * Default http proxy is used if a scheme-specific one is not found. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testDefaultProxyExplictPort() throws Exception { + System.setProperty("ftp.proxyHost", "httpproxy.com"); + System.setProperty("ftp.proxyPort", "8080"); + System.setProperty("proxyHost", "defaultproxy.com"); + System.setProperty("proxyPort", "8080"); + checkMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + checkMapping("http://example.com/", "PROXY defaultproxy.com:8080"); + checkMapping("https://example.com/", "PROXY defaultproxy.com:8080"); + } + + /** + * SOCKS proxy is used if scheme-specific one is not found. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testFallbackToSocks() throws Exception { + System.setProperty("http.proxyHost", "defaultproxy.com"); + System.setProperty("socksProxyHost", "socksproxy.com"); + checkMapping("ftp://example.com", "SOCKS5 socksproxy.com:1080"); + checkMapping("http://example.com/", "PROXY defaultproxy.com:80"); + checkMapping("https://example.com/", "SOCKS5 socksproxy.com:1080"); + } + + /** + * SOCKS proxy port is used if specified + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testSocksExplicitPort() throws Exception { + System.setProperty("socksProxyHost", "socksproxy.com"); + System.setProperty("socksProxyPort", "9000"); + checkMapping("http://example.com/", "SOCKS5 socksproxy.com:9000"); + } + + /** + * SOCKS proxy is ignored if default HTTP proxy defined. + * + * @throws Exception + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpProxySupercedesSocks() throws Exception { + System.setProperty("proxyHost", "defaultproxy.com"); + System.setProperty("socksProxyHost", "socksproxy.com"); + System.setProperty("socksProxyPort", "9000"); + checkMapping("http://example.com/", "PROXY defaultproxy.com:80"); + } +} + diff --git a/chromium/net/android/javatests/src/org/chromium/net/NetErrorsTest.java b/chromium/net/android/javatests/src/org/chromium/net/NetErrorsTest.java new file mode 100644 index 00000000000..57885b2efe0 --- /dev/null +++ b/chromium/net/android/javatests/src/org/chromium/net/NetErrorsTest.java @@ -0,0 +1,32 @@ +// 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. + +/** + * Tests to verify that NetError.java is created succesfully. + */ + +package org.chromium.net; + +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.chromium.base.test.util.Feature; + +public class NetErrorsTest extends InstrumentationTestCase { + // These are manually copied and should be kept in sync with net_error_list.h. + private static int IO_PENDING_ERROR = -1; + private static int FAILED_ERROR = -2; + + /** + * Test whether we can include NetError.java and call to static integers defined in the file. + * + * @throws Exception + */ + @SmallTest + @Feature({"Android-AppBase"}) + public void testExampleErrorDefined() throws Exception { + assertEquals(IO_PENDING_ERROR, NetError.ERR_IO_PENDING); + assertEquals(FAILED_ERROR, NetError.ERR_FAILED); + } +} diff --git a/chromium/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java b/chromium/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java new file mode 100644 index 00000000000..b52d184de67 --- /dev/null +++ b/chromium/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java @@ -0,0 +1,134 @@ +// 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 android.content.Intent; +import android.net.ConnectivityManager; +import android.telephony.TelephonyManager; +import android.test.InstrumentationTestCase; +import android.test.UiThreadTest; +import android.test.suitebuilder.annotation.MediumTest; + +import org.chromium.base.ActivityStatus; +import org.chromium.base.test.util.Feature; + +public class NetworkChangeNotifierTest extends InstrumentationTestCase { + /** + * Listens for alerts fired by the NetworkChangeNotifier when network status changes. + */ + private static class NetworkChangeNotifierTestObserver + implements NetworkChangeNotifier.ConnectionTypeObserver { + private boolean mReceivedNotification = false; + + @Override + public void onConnectionTypeChanged(int connectionType) { + mReceivedNotification = true; + } + + public boolean hasReceivedNotification() { + return mReceivedNotification; + } + + public void resetHasReceivedNotification() { + mReceivedNotification = false; + } + } + + /** + * Mocks out calls to the ConnectivityManager. + */ + class MockConnectivityManagerDelegate + extends NetworkChangeNotifierAutoDetect.ConnectivityManagerDelegate { + private boolean mActiveNetworkExists; + private int mNetworkType; + private int mNetworkSubtype; + + @Override + boolean activeNetworkExists() { + return mActiveNetworkExists; + } + + @Override + boolean isConnected() { + return getNetworkType() != NetworkChangeNotifier.CONNECTION_NONE; + } + + void setActiveNetworkExists(boolean networkExists) { + mActiveNetworkExists = networkExists; + } + + @Override + int getNetworkType() { + return mNetworkType; + } + + void setNetworkType(int networkType) { + mNetworkType = networkType; + } + + @Override + int getNetworkSubtype() { + return mNetworkSubtype; + } + + void setNetworkSubtype(int networkSubtype) { + mNetworkSubtype = networkSubtype; + } + } + + /** + * Tests that when Chrome gets an intent indicating a change in network connectivity, it sends a + * notification to Java observers. + */ + @UiThreadTest + @MediumTest + @Feature({"Android-AppBase"}) + public void testNetworkChangeNotifierJavaObservers() throws InterruptedException { + // Create a new notifier that doesn't have a native-side counterpart. + Context context = getInstrumentation().getTargetContext(); + NetworkChangeNotifier.resetInstanceForTests(context); + + NetworkChangeNotifier.setAutoDetectConnectivityState(true); + NetworkChangeNotifierAutoDetect receiver = NetworkChangeNotifier.getAutoDetectorForTest(); + assertTrue(receiver != null); + + MockConnectivityManagerDelegate connectivityDelegate = + new MockConnectivityManagerDelegate(); + connectivityDelegate.setActiveNetworkExists(true); + connectivityDelegate.setNetworkType(NetworkChangeNotifier.CONNECTION_UNKNOWN); + connectivityDelegate.setNetworkSubtype(TelephonyManager.NETWORK_TYPE_UNKNOWN); + receiver.setConnectivityManagerDelegateForTests(connectivityDelegate); + + // Initialize the NetworkChangeNotifier with a connection. + Intent connectivityIntent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); + receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent); + + // We shouldn't be re-notified if the connection hasn't actually changed. + NetworkChangeNotifierTestObserver observer = new NetworkChangeNotifierTestObserver(); + NetworkChangeNotifier.addConnectionTypeObserver(observer); + receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent); + assertFalse(observer.hasReceivedNotification()); + + // Mimic that connectivity has been lost and ensure that Chrome notifies our observer. + connectivityDelegate.setActiveNetworkExists(false); + connectivityDelegate.setNetworkType(NetworkChangeNotifier.CONNECTION_NONE); + Intent noConnectivityIntent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); + receiver.onReceive(getInstrumentation().getTargetContext(), noConnectivityIntent); + assertTrue(observer.hasReceivedNotification()); + + observer.resetHasReceivedNotification(); + // Pretend we got moved to the background. + receiver.onActivityStateChange(ActivityStatus.PAUSED); + // Change the state. + connectivityDelegate.setActiveNetworkExists(true); + connectivityDelegate.setNetworkType(NetworkChangeNotifier.CONNECTION_WIFI); + // The NetworkChangeNotifierAutoDetect doesn't receive any notification while we are in the + // background, but when we get back to the foreground the state changed should be detected + // and a notification sent. + receiver.onActivityStateChange(ActivityStatus.RESUMED); + assertTrue(observer.hasReceivedNotification()); + } +} diff --git a/chromium/net/android/javatests/src/org/chromium/net/X509UtilTest.java b/chromium/net/android/javatests/src/org/chromium/net/X509UtilTest.java new file mode 100644 index 00000000000..7dcbc685cd4 --- /dev/null +++ b/chromium/net/android/javatests/src/org/chromium/net/X509UtilTest.java @@ -0,0 +1,102 @@ +// Copyright 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.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.telephony.TelephonyManager; +import android.test.UiThreadTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.InstrumentationTestCase; +import android.util.Base64; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.security.GeneralSecurityException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +import org.chromium.base.PathUtils; + +/** + * Tests for org.chromium.net.X509Util. + */ +public class X509UtilTest extends InstrumentationTestCase { + private static final String CERTS_DIRECTORY = + PathUtils.getExternalStorageDirectory() + "/net/data/ssl/certificates/"; + private static final String BAD_EKU_TEST_ROOT = "eku-test-root.pem"; + private static final String CRITICAL_CODE_SIGNING_EE = "crit-codeSigning-chain.pem"; + private static final String NON_CRITICAL_CODE_SIGNING_EE = "non-crit-codeSigning-chain.pem"; + private static final String WEB_CLIENT_AUTH_EE = "invalid_key_usage_cert.der"; + private static final String OK_CERT = "ok_cert.pem"; + private static final String GOOD_ROOT_CA = "root_ca_cert.pem"; + + private static final String BEGIN_MARKER = "-----BEGIN CERTIFICATE-----"; + private static final String END_MARKER = "-----END CERTIFICATE-----"; + + private static byte[] pemToDer(String pemPathname) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(pemPathname)); + StringBuilder builder = new StringBuilder(); + + // Skip past leading junk lines, if any. + String line = reader.readLine(); + while (line != null && !line.contains(BEGIN_MARKER)) line = reader.readLine(); + + // Then skip the BEGIN_MARKER itself, if present. + while (line != null && line.contains(BEGIN_MARKER)) line = reader.readLine(); + + // Now gather the data lines into the builder. + while (line != null && !line.contains(END_MARKER)) { + builder.append(line.trim()); + line = reader.readLine(); + } + + reader.close(); + return Base64.decode(builder.toString(), Base64.DEFAULT); + } + + private static byte[] readFileBytes(String pathname) throws IOException { + RandomAccessFile file = new RandomAccessFile(pathname, "r"); + byte[] bytes = new byte[(int) file.length()]; + int bytesRead = file.read(bytes); + if (bytesRead != bytes.length) + return Arrays.copyOfRange(bytes, 0, bytesRead); + return bytes; + } + + @MediumTest + public void testEkusVerified() throws GeneralSecurityException, IOException { + X509Util.addTestRootCertificate(pemToDer(CERTS_DIRECTORY + BAD_EKU_TEST_ROOT)); + X509Util.addTestRootCertificate(pemToDer(CERTS_DIRECTORY + GOOD_ROOT_CA)); + + assertFalse(X509Util.verifyKeyUsage( + X509Util.createCertificateFromBytes( + pemToDer(CERTS_DIRECTORY + CRITICAL_CODE_SIGNING_EE)))); + + assertFalse(X509Util.verifyKeyUsage( + X509Util.createCertificateFromBytes( + pemToDer(CERTS_DIRECTORY + NON_CRITICAL_CODE_SIGNING_EE)))); + + assertFalse(X509Util.verifyKeyUsage( + X509Util.createCertificateFromBytes( + readFileBytes(CERTS_DIRECTORY + WEB_CLIENT_AUTH_EE)))); + + assertTrue(X509Util.verifyKeyUsage( + X509Util.createCertificateFromBytes( + pemToDer(CERTS_DIRECTORY + OK_CERT)))); + + try { + X509Util.clearTestRootCertificates(); + } catch (Exception e) { + fail("Could not clear test root certificates: " + e.toString()); + } + } +} + diff --git a/chromium/net/android/keystore.cc b/chromium/net/android/keystore.cc new file mode 100644 index 00000000000..a3d8cc1771f --- /dev/null +++ b/chromium/net/android/keystore.cc @@ -0,0 +1,130 @@ +// 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. + +#include "net/android/keystore.h" + +#include <vector> + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/logging.h" + +#include "jni/AndroidKeyStore_jni.h" + +using base::android::AttachCurrentThread; +using base::android::HasException; +using base::android::JavaByteArrayToByteVector; +using base::android::ScopedJavaLocalRef; +using base::android::ToJavaByteArray; +using base::android::JavaArrayOfByteArrayToStringVector; + +namespace net { +namespace android { + +bool GetRSAKeyModulus( + jobject private_key_ref, + std::vector<uint8>* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jbyteArray> modulus_ref = + Java_AndroidKeyStore_getRSAKeyModulus(env, private_key_ref); + if (modulus_ref.is_null()) + return false; + + JavaByteArrayToByteVector(env, modulus_ref.obj(), result); + return true; +} + +bool GetDSAKeyParamQ(jobject private_key_ref, + std::vector<uint8>* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jbyteArray> q_ref = + Java_AndroidKeyStore_getDSAKeyParamQ(env, private_key_ref); + if (q_ref.is_null()) + return false; + + JavaByteArrayToByteVector(env, q_ref.obj(), result); + return true; +} + +bool GetECKeyOrder(jobject private_key_ref, + std::vector<uint8>* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jbyteArray> order_ref = + Java_AndroidKeyStore_getECKeyOrder(env, private_key_ref); + if (order_ref.is_null()) + return false; + + JavaByteArrayToByteVector(env, order_ref.obj(), result); + return true; +} + +bool GetPrivateKeyEncodedBytes(jobject private_key, + std::vector<uint8>* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jbyteArray> encoded_ref = + Java_AndroidKeyStore_getPrivateKeyEncodedBytes(env, private_key); + if (encoded_ref.is_null()) + return false; + + JavaByteArrayToByteVector(env, encoded_ref.obj(), result); + return true; +} + +bool RawSignDigestWithPrivateKey( + jobject private_key_ref, + const base::StringPiece& digest, + std::vector<uint8>* signature) { + JNIEnv* env = AttachCurrentThread(); + + // Convert message to byte[] array. + ScopedJavaLocalRef<jbyteArray> digest_ref = + ToJavaByteArray(env, + reinterpret_cast<const uint8*>(digest.data()), + digest.length()); + DCHECK(!digest_ref.is_null()); + + // Invoke platform API + ScopedJavaLocalRef<jbyteArray> signature_ref = + Java_AndroidKeyStore_rawSignDigestWithPrivateKey( + env, private_key_ref, digest_ref.obj()); + if (HasException(env) || signature_ref.is_null()) + return false; + + // Write signature to string. + JavaByteArrayToByteVector(env, signature_ref.obj(), signature); + return true; +} + +PrivateKeyType GetPrivateKeyType(jobject private_key) { + JNIEnv* env = AttachCurrentThread(); + int type = Java_AndroidKeyStore_getPrivateKeyType( + env, private_key); + return static_cast<PrivateKeyType>(type); +} + +EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key) { + JNIEnv* env = AttachCurrentThread(); + // Note: the pointer is passed as a jint here because that's how it + // is stored in the Java object. Java doesn't have a primitive type + // like intptr_t that matches the size of pointers on the host + // machine, and Android only runs on 32-bit CPUs. + // + // Given that this routine shall only be called on Android < 4.2, + // this won't be a problem in the far future (e.g. when Android gets + // ported to 64-bit environments, if ever). + int pkey = + Java_AndroidKeyStore_getOpenSSLHandleForPrivateKey(env, private_key); + return reinterpret_cast<EVP_PKEY*>(pkey); +} + +bool RegisterKeyStore(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace net diff --git a/chromium/net/android/keystore.h b/chromium/net/android/keystore.h new file mode 100644 index 00000000000..f14fa872edd --- /dev/null +++ b/chromium/net/android/keystore.h @@ -0,0 +1,117 @@ +// 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. + +#ifndef NET_ANDROID_KEYSTORE_H +#define NET_ANDROID_KEYSTORE_H + +#include <jni.h> + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/ssl/ssl_client_cert_type.h" + +// Avoid including <openssl/evp.h> here. +typedef struct evp_pkey_st EVP_PKEY; + +// Misc functions to access the Android platform KeyStore. + +namespace net { +namespace android { + +// Define a list of constants describing private key types. The +// values are shared with Java through org.chromium.net.PrivateKeyType. +// Example: PRIVATE_KEY_TYPE_RSA. +enum PrivateKeyType { +#define DEFINE_PRIVATE_KEY_TYPE(name,value) PRIVATE_KEY_TYPE_ ## name = value, +#include "net/android/private_key_type_list.h" +#undef DEFINE_PRIVATE_KEY_TYPE +}; + +// Returns the modulus of a given RSAPrivateKey platform object, +// as a series of bytes, in big-endian representation. This can be +// used with BN_bin2bn() to convert to an OpenSSL BIGNUM. +// +// |private_key| is a JNI reference for the private key. +// |modulus| will receive the modulus bytes on success. +// Returns true on success, or false on failure (e.g. if the key +// is not RSA). +NET_EXPORT bool GetRSAKeyModulus(jobject private_key, + std::vector<uint8>* modulus); + +// Returns the Q parameter of a given DSAPrivateKey platform object, +// as a series of bytes, in big-endian representation. This can be used +// with BN_bin2bn() to convert to an OpenSSL BIGNUM. +// |private_key| is a JNI reference for the private key. +// |q| will receive the result bytes on success. +// Returns true on success, or false on failure (e.g. if the key is +// not DSA). +NET_EXPORT bool GetDSAKeyParamQ(jobject private_key, + std::vector<uint8>* q); + +// Returns the order parameter of a given ECPrivateKey platform object, +// as a series of bytes, in big-endian representation. This can be used +// with BN_bin2bn() to convert to an OpenSSL BIGNUM. +// |private_key| is a JNI reference for the private key. +// |order| will receive the result bytes on success. +// Returns true on success, or false on failure (e.g. if the key is +// not EC). +bool GetECKeyOrder(jobject private_key, + std::vector<uint8>* order); + +// Returns the encoded PKCS#8 representation of a private key. +// This only works on Android 4.0.3 and older releases for platform keys +// (i.e. all keys except those explicitely generated by the application). +// |private_key| is a JNI reference for the private key. +// |encoded| will receive the encoded data on success. +// Returns true on success, or false on failure (e.g. on 4.0.4 or higher). +bool GetPrivateKeyEncodedBytes(jobject private_key, + std::vector<uint8>* encoded); + +// Compute the signature of a given message, which is actually a hash, +// using a private key. For more details, please read the comments for the +// rawSignDigestWithPrivateKey method in AndroidKeyStore.java. +// +// |private_key| is a JNI reference for the private key. +// |digest| is the input digest. +// |signature| will receive the signature on success. +// Returns true on success, false on failure. +// +NET_EXPORT bool RawSignDigestWithPrivateKey( + jobject private_key, + const base::StringPiece& digest, + std::vector<uint8>* signature); + + +// Return the PrivateKeyType of a given private key. +// |private_key| is a JNI reference for the private key. +// Returns a PrivateKeyType, while will be CLIENT_CERT_INVALID_TYPE +// on error. +NET_EXPORT PrivateKeyType GetPrivateKeyType(jobject private_key); + +// Returns a handle to the system EVP_PKEY object used to back a given +// private_key object. This must *only* be used for RSA private keys +// on Android < 4.2. Technically, this is only guaranteed to work if +// the system image contains a vanilla implementation of the Java +// API frameworks based on Harmony + OpenSSL. +// +// |private_key| is a JNI reference for the private key. +// Returns an EVP_PKEY* handle, or NULL in case of error. +// +// Note: Despite its name and return type, this function doesn't know +// anything about OpenSSL, it just type-casts a system pointer that +// is passed as an int through JNI. As such, it never increments +// the returned key's reference count. +EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key); + +// Register JNI methods +NET_EXPORT bool RegisterKeyStore(JNIEnv* env); + +} // namespace android +} // namespace net + +#endif // NET_ANDROID_KEYSTORE_H diff --git a/chromium/net/android/keystore_openssl.cc b/chromium/net/android/keystore_openssl.cc new file mode 100644 index 00000000000..cc463f49539 --- /dev/null +++ b/chromium/net/android/keystore_openssl.cc @@ -0,0 +1,699 @@ +// 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. + +#include "net/android/keystore_openssl.h" + +#include <jni.h> +#include <openssl/bn.h> +// This include is required to get the ECDSA_METHOD structure definition +// which isn't currently part of the OpenSSL official ABI. This should +// not be a concern for Chromium which always links against its own +// version of the library on Android. +#include <openssl/crypto/ecdsa/ecs_locl.h> +// And this one is needed for the EC_GROUP definition. +#include <openssl/crypto/ec/ec_lcl.h> +#include <openssl/dsa.h> +#include <openssl/ec.h> +#include <openssl/engine.h> +#include <openssl/evp.h> +#include <openssl/rsa.h> + +#include "base/android/build_info.h" +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "crypto/openssl_util.h" +#include "net/android/keystore.h" +#include "net/ssl/ssl_client_cert_type.h" + +// IMPORTANT NOTE: The following code will currently only work when used +// to implement client certificate support with OpenSSL. That's because +// only the signing operations used in this use case are implemented here. +// +// Generally speaking, OpenSSL provides many different ways to sign +// digests. This code doesn't support all these cases, only the ones that +// are required to sign the MAC during the OpenSSL handshake for TLS < 1.2. +// +// The OpenSSL EVP_PKEY type is a generic wrapper around key pairs. +// Internally, it can hold a pointer to a RSA, DSA or ECDSA structure, +// which model keypair implementations of each respective crypto +// algorithm. +// +// The RSA type has a 'method' field pointer to a vtable-like structure +// called a RSA_METHOD. This contains several function pointers that +// correspond to operations on RSA keys (e.g. decode/encode with public +// key, decode/encode with private key, signing, validation), as well as +// a few flags. +// +// For example, the RSA_sign() function will call "method->rsa_sign()" if +// method->rsa_sign is not NULL, otherwise, it will perform a regular +// signing operation using the other fields in the RSA structure (which +// are used to hold the typical modulus / exponent / parameters for the +// key pair). +// +// This source file thus defines a custom RSA_METHOD structure, which +// fields points to static methods used to implement the corresponding +// RSA operation using platform Android APIs. +// +// However, the platform APIs require a jobject JNI reference to work. +// It must be stored in the RSA instance, or made accessible when the +// custom RSA methods are called. This is done by using RSA_set_app_data() +// and RSA_get_app_data(). +// +// One can thus _directly_ create a new EVP_PKEY that uses a custom RSA +// object with the following: +// +// RSA* rsa = RSA_new() +// RSA_set_method(&custom_rsa_method); +// RSA_set_app_data(rsa, jni_private_key); +// +// EVP_PKEY* pkey = EVP_PKEY_new(); +// EVP_PKEY_assign_RSA(pkey, rsa); +// +// Note that because EVP_PKEY_assign_RSA() is used, instead of +// EVP_PKEY_set1_RSA(), the new EVP_PKEY now owns the RSA object, and +// will destroy it when it is itself destroyed. +// +// Unfortunately, such objects cannot be used with RSA_size(), which +// totally ignores the RSA_METHOD pointers. Instead, it is necessary +// to manually setup the modulus field (n) in the RSA object, with a +// value that matches the wrapped PrivateKey object. See GetRsaPkeyWrapper +// for full details. +// +// Similarly, custom DSA_METHOD and ECDSA_METHOD are defined by this source +// file, and appropriate field setups are performed to ensure that +// DSA_size() and ECDSA_size() work properly with the wrapper EVP_PKEY. +// +// Note that there is no need to define an OpenSSL ENGINE here. These +// are objects that can be used to expose custom methods (i.e. either +// RSA_METHOD, DSA_METHOD, ECDSA_METHOD, and a large number of other ones +// for types not related to this source file), and make them used by +// default for a lot of operations. Very fortunately, this is not needed +// here, which saves a lot of complexity. + +using base::android::ScopedJavaGlobalRef; + +namespace net { +namespace android { + +namespace { + +typedef crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ScopedEVP_PKEY; +typedef crypto::ScopedOpenSSL<RSA, RSA_free> ScopedRSA; +typedef crypto::ScopedOpenSSL<DSA, DSA_free> ScopedDSA; +typedef crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ScopedEC_KEY; +typedef crypto::ScopedOpenSSL<EC_GROUP, EC_GROUP_free> ScopedEC_GROUP; + +// Custom RSA_METHOD that uses the platform APIs. +// Note that for now, only signing through RSA_sign() is really supported. +// all other method pointers are either stubs returning errors, or no-ops. +// See <openssl/rsa.h> for exact declaration of RSA_METHOD. + +int RsaMethodPubEnc(int flen, + const unsigned char* from, + unsigned char* to, + RSA* rsa, + int padding) { + NOTIMPLEMENTED(); + RSAerr(RSA_F_RSA_PUBLIC_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); + return -1; +} + +int RsaMethodPubDec(int flen, + const unsigned char* from, + unsigned char* to, + RSA* rsa, + int padding) { + NOTIMPLEMENTED(); + RSAerr(RSA_F_RSA_PUBLIC_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); + return -1; +} + +int RsaMethodPrivEnc(int flen, + const unsigned char *from, + unsigned char *to, + RSA *rsa, + int padding) { + NOTIMPLEMENTED(); + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); + return -1; +} + +int RsaMethodPrivDec(int flen, + const unsigned char* from, + unsigned char* to, + RSA* rsa, + int padding) { + NOTIMPLEMENTED(); + RSAerr(RSA_F_RSA_PRIVATE_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); + return -1; +} + +int RsaMethodInit(RSA* rsa) { + // Required to ensure that RsaMethodSign will be called. + rsa->flags |= RSA_FLAG_SIGN_VER; + return 0; +} + +int RsaMethodFinish(RSA* rsa) { + // Ensure the global JNI reference created with this wrapper is + // properly destroyed with it. + jobject key = reinterpret_cast<jobject>(RSA_get_app_data(rsa)); + if (key != NULL) { + RSA_set_app_data(rsa, NULL); + JNIEnv* env = base::android::AttachCurrentThread(); + env->DeleteGlobalRef(key); + } + // Actual return value is ignored by OpenSSL. There are no docs + // explaining what this is supposed to be. + return 0; +} + +int RsaMethodSign(int type, + const unsigned char* message, + unsigned int message_len, + unsigned char* signature, + unsigned int* signature_len, + const RSA* rsa) { + // This is only used for client certificate support, which + // will always pass the NID_md5_sha1 |type| value. + DCHECK_EQ(NID_md5_sha1, type); + if (type != NID_md5_sha1) { + RSAerr(RSA_F_RSA_SIGN, RSA_R_UNKNOWN_ALGORITHM_TYPE); + return 0; + } + // Retrieve private key JNI reference. + jobject private_key = reinterpret_cast<jobject>(RSA_get_app_data(rsa)); + if (!private_key) { + LOG(WARNING) << "Null JNI reference passed to RsaMethodSign!"; + return 0; + } + // Sign message with it through JNI. + base::StringPiece message_piece(reinterpret_cast<const char*>(message), + static_cast<size_t>(message_len)); + std::vector<uint8> result; + + if (!RawSignDigestWithPrivateKey( + private_key, message_piece, &result)) { + LOG(WARNING) << "Could not sign message in RsaMethodSign!"; + return 0; + } + + size_t expected_size = static_cast<size_t>(RSA_size(rsa)); + if (result.size() > expected_size) { + LOG(ERROR) << "RSA Signature size mismatch, actual: " + << result.size() << ", expected <= " << expected_size; + return 0; + } + + // Copy result to OpenSSL-provided buffer + memcpy(signature, &result[0], result.size()); + *signature_len = static_cast<unsigned int>(result.size()); + return 1; +} + +const RSA_METHOD android_rsa_method = { + /* .name = */ "Android signing-only RSA method", + /* .rsa_pub_enc = */ RsaMethodPubEnc, + /* .rsa_pub_dec = */ RsaMethodPubDec, + /* .rsa_priv_enc = */ RsaMethodPrivEnc, + /* .rsa_priv_dec = */ RsaMethodPrivDec, + /* .rsa_mod_exp = */ NULL, + /* .bn_mod_exp = */ NULL, + /* .init = */ RsaMethodInit, + /* .finish = */ RsaMethodFinish, + // This flag is necessary to tell OpenSSL to avoid checking the content + // (i.e. internal fields) of the private key. Otherwise, it will complain + // it's not valid for the certificate. + /* .flags = */ RSA_METHOD_FLAG_NO_CHECK, + /* .app_data = */ NULL, + /* .rsa_sign = */ RsaMethodSign, + /* .rsa_verify = */ NULL, + /* .rsa_keygen = */ NULL, +}; + +// Copy the contents of an encoded big integer into an existing BIGNUM. +// This function modifies |*num| in-place. +// |new_bytes| is the byte encoding of the new value. +// |num| points to the BIGNUM which will be assigned with the new value. +// Returns true on success, false otherwise. On failure, |*num| is +// not modified. +bool CopyBigNumFromBytes(const std::vector<uint8>& new_bytes, + BIGNUM* num) { + BIGNUM* ret = BN_bin2bn( + reinterpret_cast<const unsigned char*>(&new_bytes[0]), + static_cast<int>(new_bytes.size()), + num); + return (ret != NULL); +} + +// Decode the contents of an encoded big integer and either create a new +// BIGNUM object (if |*num_ptr| is NULL on input) or copy it (if +// |*num_ptr| is not NULL). +// |new_bytes| is the byte encoding of the new value. +// |num_ptr| is the address of a BIGNUM pointer. |*num_ptr| can be NULL. +// Returns true on success, false otherwise. On failure, |*num_ptr| is +// not modified. On success, |*num_ptr| will always be non-NULL and +// point to a valid BIGNUM object. +bool SwapBigNumPtrFromBytes(const std::vector<uint8>& new_bytes, + BIGNUM** num_ptr) { + BIGNUM* old_num = *num_ptr; + BIGNUM* new_num = BN_bin2bn( + reinterpret_cast<const unsigned char*>(&new_bytes[0]), + static_cast<int>(new_bytes.size()), + old_num); + if (new_num == NULL) + return false; + + if (old_num == NULL) + *num_ptr = new_num; + return true; +} + +// Setup an EVP_PKEY to wrap an existing platform RSA PrivateKey object. +// |private_key| is the JNI reference (local or global) to the object. +// |pkey| is the EVP_PKEY to setup as a wrapper. +// Returns true on success, false otherwise. +// On success, this creates a new global JNI reference to the object +// that is owned by and destroyed with the EVP_PKEY. I.e. caller can +// free |private_key| after the call. +// IMPORTANT: The EVP_PKEY will *only* work on Android >= 4.2. For older +// platforms, use GetRsaLegacyKey() instead. +bool GetRsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) { + ScopedRSA rsa(RSA_new()); + RSA_set_method(rsa.get(), &android_rsa_method); + + // HACK: RSA_size() doesn't work with custom RSA_METHODs. To ensure that + // it will return the right value, set the 'n' field of the RSA object + // to match the private key's modulus. + std::vector<uint8> modulus; + if (!GetRSAKeyModulus(private_key, &modulus)) { + LOG(ERROR) << "Failed to get private key modulus"; + return false; + } + if (!SwapBigNumPtrFromBytes(modulus, &rsa.get()->n)) { + LOG(ERROR) << "Failed to decode private key modulus"; + return false; + } + + ScopedJavaGlobalRef<jobject> global_key; + global_key.Reset(NULL, private_key); + if (global_key.is_null()) { + LOG(ERROR) << "Could not create global JNI reference"; + return false; + } + RSA_set_app_data(rsa.get(), global_key.Release()); + EVP_PKEY_assign_RSA(pkey, rsa.release()); + return true; +} + +// Setup an EVP_PKEY to wrap an existing platform RSA PrivateKey object +// for Android 4.0 to 4.1.x. Must only be used on Android < 4.2. +// |private_key| is a JNI reference (local or global) to the object. +// |pkey| is the EVP_PKEY to setup as a wrapper. +// Returns true on success, false otherwise. +EVP_PKEY* GetRsaLegacyKey(jobject private_key) { + EVP_PKEY* sys_pkey = + GetOpenSSLSystemHandleForPrivateKey(private_key); + if (sys_pkey != NULL) { + CRYPTO_add(&sys_pkey->references, 1, CRYPTO_LOCK_EVP_PKEY); + } else { + // GetOpenSSLSystemHandleForPrivateKey() will fail on Android + // 4.0.3 and earlier. However, it is possible to get the key + // content with PrivateKey.getEncoded() on these platforms. + // Note that this method may return NULL on 4.0.4 and later. + std::vector<uint8> encoded; + if (!GetPrivateKeyEncodedBytes(private_key, &encoded)) { + LOG(ERROR) << "Can't get private key data!"; + return NULL; + } + const unsigned char* p = + reinterpret_cast<const unsigned char*>(&encoded[0]); + int len = static_cast<int>(encoded.size()); + sys_pkey = d2i_AutoPrivateKey(NULL, &p, len); + if (sys_pkey == NULL) { + LOG(ERROR) << "Can't convert private key data!"; + return NULL; + } + } + return sys_pkey; +} + +// Custom DSA_METHOD that uses the platform APIs. +// Note that for now, only signing through DSA_sign() is really supported. +// all other method pointers are either stubs returning errors, or no-ops. +// See <openssl/dsa.h> for exact declaration of DSA_METHOD. +// +// Note: There is no DSA_set_app_data() and DSA_get_app_data() functions, +// but RSA_set_app_data() is defined as a simple macro that calls +// RSA_set_ex_data() with a hard-coded index of 0, so this code +// does the same thing here. + +DSA_SIG* DsaMethodDoSign(const unsigned char* dgst, + int dlen, + DSA* dsa) { + // Extract the JNI reference to the PrivateKey object. + jobject private_key = reinterpret_cast<jobject>(DSA_get_ex_data(dsa, 0)); + if (private_key == NULL) + return NULL; + + // Sign the message with it, calling platform APIs. + std::vector<uint8> signature; + if (!RawSignDigestWithPrivateKey( + private_key, + base::StringPiece( + reinterpret_cast<const char*>(dgst), + static_cast<size_t>(dlen)), + &signature)) { + return NULL; + } + + // Note: With DSA, the actual signature might be smaller than DSA_size(). + size_t max_expected_size = static_cast<size_t>(DSA_size(dsa)); + if (signature.size() > max_expected_size) { + LOG(ERROR) << "DSA Signature size mismatch, actual: " + << signature.size() << ", expected <= " + << max_expected_size; + return NULL; + } + + // Convert the signature into a DSA_SIG object. + const unsigned char* sigbuf = + reinterpret_cast<const unsigned char*>(&signature[0]); + int siglen = static_cast<size_t>(signature.size()); + DSA_SIG* dsa_sig = d2i_DSA_SIG(NULL, &sigbuf, siglen); + return dsa_sig; +} + +int DsaMethodSignSetup(DSA* dsa, + BN_CTX* ctx_in, + BIGNUM** kinvp, + BIGNUM** rp) { + NOTIMPLEMENTED(); + DSAerr(DSA_F_DSA_SIGN_SETUP, DSA_R_INVALID_DIGEST_TYPE); + return -1; +} + +int DsaMethodDoVerify(const unsigned char* dgst, + int dgst_len, + DSA_SIG* sig, + DSA* dsa) { + NOTIMPLEMENTED(); + DSAerr(DSA_F_DSA_DO_VERIFY, DSA_R_INVALID_DIGEST_TYPE); + return -1; +} + +int DsaMethodFinish(DSA* dsa) { + // Free the global JNI reference that was created with this + // wrapper key. + jobject key = reinterpret_cast<jobject>(DSA_get_ex_data(dsa,0)); + if (key != NULL) { + DSA_set_ex_data(dsa, 0, NULL); + JNIEnv* env = base::android::AttachCurrentThread(); + env->DeleteGlobalRef(key); + } + // Actual return value is ignored by OpenSSL. There are no docs + // explaining what this is supposed to be. + return 0; +} + +const DSA_METHOD android_dsa_method = { + /* .name = */ "Android signing-only DSA method", + /* .dsa_do_sign = */ DsaMethodDoSign, + /* .dsa_sign_setup = */ DsaMethodSignSetup, + /* .dsa_do_verify = */ DsaMethodDoVerify, + /* .dsa_mod_exp = */ NULL, + /* .bn_mod_exp = */ NULL, + /* .init = */ NULL, // nothing to do here. + /* .finish = */ DsaMethodFinish, + /* .flags = */ 0, + /* .app_data = */ NULL, + /* .dsa_paramgem = */ NULL, + /* .dsa_keygen = */ NULL +}; + +// Setup an EVP_PKEY to wrap an existing DSA platform PrivateKey object. +// |private_key| is a JNI reference (local or global) to the object. +// |pkey| is the EVP_PKEY to setup as a wrapper. +// Returns true on success, false otherwise. +// On success, this creates a global JNI reference to the same object +// that will be owned by and destroyed with the EVP_PKEY. +bool GetDsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) { + ScopedDSA dsa(DSA_new()); + DSA_set_method(dsa.get(), &android_dsa_method); + + // DSA_size() doesn't work with custom DSA_METHODs. To ensure it + // returns the right value, set the 'q' field in the DSA object to + // match the parameter from the platform key. + std::vector<uint8> q; + if (!GetDSAKeyParamQ(private_key, &q)) { + LOG(ERROR) << "Can't extract Q parameter from DSA private key"; + return false; + } + if (!SwapBigNumPtrFromBytes(q, &dsa.get()->q)) { + LOG(ERROR) << "Can't decode Q parameter from DSA private key"; + return false; + } + + ScopedJavaGlobalRef<jobject> global_key; + global_key.Reset(NULL, private_key); + if (global_key.is_null()) { + LOG(ERROR) << "Could not create global JNI reference"; + return false; + } + DSA_set_ex_data(dsa.get(), 0, global_key.Release()); + EVP_PKEY_assign_DSA(pkey, dsa.release()); + return true; +} + +// Custom ECDSA_METHOD that uses the platform APIs. +// Note that for now, only signing through ECDSA_sign() is really supported. +// all other method pointers are either stubs returning errors, or no-ops. +// +// Note: The ECDSA_METHOD structure doesn't have init/finish +// methods. As such, the only way to to ensure the global +// JNI reference is properly released when the EVP_PKEY is +// destroyed is to use a custom EX_DATA type. + +// Used to ensure that the global JNI reference associated with a custom +// EC_KEY + ECDSA_METHOD wrapper is released when its EX_DATA is destroyed +// (this function is called when EVP_PKEY_free() is called on the wrapper). +void ExDataFree(void* parent, + void* ptr, + CRYPTO_EX_DATA* ad, + int idx, + long argl, + void* argp) { + jobject private_key = reinterpret_cast<jobject>(ptr); + if (private_key == NULL) + return; + + CRYPTO_set_ex_data(ad, idx, NULL); + + JNIEnv* env = base::android::AttachCurrentThread(); + env->DeleteGlobalRef(private_key); +} + +int ExDataDup(CRYPTO_EX_DATA* to, + CRYPTO_EX_DATA* from, + void* from_d, + int idx, + long argl, + void* argp) { + // This callback shall never be called with the current OpenSSL + // implementation (the library only ever duplicates EX_DATA items + // for SSL and BIO objects). But provide this to catch regressions + // in the future. + CHECK(false) << "ExDataDup was called for ECDSA custom key !?"; + // Return value is currently ignored by OpenSSL. + return 0; +} + +class EcdsaExDataIndex { +public: + int ex_data_index() { return ex_data_index_; } + + EcdsaExDataIndex() { + ex_data_index_ = ECDSA_get_ex_new_index(0, // argl + NULL, // argp + NULL, // new_func + ExDataDup, // dup_func + ExDataFree); // free_func + } + +private: + int ex_data_index_; +}; + +// Returns the index of the custom EX_DATA used to store the JNI reference. +int EcdsaGetExDataIndex(void) { + // Use a LazyInstance to perform thread-safe lazy initialization. + // Use a leaky one, since OpenSSL doesn't provide a way to release + // allocated EX_DATA indices. + static base::LazyInstance<EcdsaExDataIndex>::Leaky s_instance = + LAZY_INSTANCE_INITIALIZER; + return s_instance.Get().ex_data_index(); +} + +ECDSA_SIG* EcdsaMethodDoSign(const unsigned char* dgst, + int dgst_len, + const BIGNUM* inv, + const BIGNUM* rp, + EC_KEY* eckey) { + // Retrieve private key JNI reference. + jobject private_key = reinterpret_cast<jobject>( + ECDSA_get_ex_data(eckey, EcdsaGetExDataIndex())); + if (!private_key) { + LOG(WARNING) << "Null JNI reference passed to EcdsaMethodDoSign!"; + return NULL; + } + // Sign message with it through JNI. + std::vector<uint8> signature; + base::StringPiece digest( + reinterpret_cast<const char*>(dgst), + static_cast<size_t>(dgst_len)); + if (!RawSignDigestWithPrivateKey( + private_key, digest, &signature)) { + LOG(WARNING) << "Could not sign message in EcdsaMethodDoSign!"; + return NULL; + } + + // Note: With ECDSA, the actual signature may be smaller than + // ECDSA_size(). + size_t max_expected_size = static_cast<size_t>(ECDSA_size(eckey)); + if (signature.size() > max_expected_size) { + LOG(ERROR) << "ECDSA Signature size mismatch, actual: " + << signature.size() << ", expected <= " + << max_expected_size; + return NULL; + } + + // Convert signature to ECDSA_SIG object + const unsigned char* sigbuf = + reinterpret_cast<const unsigned char*>(&signature[0]); + long siglen = static_cast<long>(signature.size()); + return d2i_ECDSA_SIG(NULL, &sigbuf, siglen); +} + +int EcdsaMethodSignSetup(EC_KEY* eckey, + BN_CTX* ctx, + BIGNUM** kinv, + BIGNUM** r) { + NOTIMPLEMENTED(); + ECDSAerr(ECDSA_F_ECDSA_SIGN_SETUP, ECDSA_R_ERR_EC_LIB); + return -1; +} + +int EcdsaMethodDoVerify(const unsigned char* dgst, + int dgst_len, + const ECDSA_SIG* sig, + EC_KEY* eckey) { + NOTIMPLEMENTED(); + ECDSAerr(ECDSA_F_ECDSA_DO_VERIFY, ECDSA_R_ERR_EC_LIB); + return -1; +} + +const ECDSA_METHOD android_ecdsa_method = { + /* .name = */ "Android signing-only ECDSA method", + /* .ecdsa_do_sign = */ EcdsaMethodDoSign, + /* .ecdsa_sign_setup = */ EcdsaMethodSignSetup, + /* .ecdsa_do_verify = */ EcdsaMethodDoVerify, + /* .flags = */ 0, + /* .app_data = */ NULL, +}; + +// Setup an EVP_PKEY to wrap an existing platform PrivateKey object. +// |private_key| is the JNI reference (local or global) to the object. +// |pkey| is the EVP_PKEY to setup as a wrapper. +// Returns true on success, false otherwise. +// On success, this creates a global JNI reference to the object that +// is owned by and destroyed with the EVP_PKEY. I.e. the caller shall +// always free |private_key| after the call. +bool GetEcdsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) { + ScopedEC_KEY eckey(EC_KEY_new()); + ECDSA_set_method(eckey.get(), &android_ecdsa_method); + + // To ensure that ECDSA_size() works properly, craft a custom EC_GROUP + // that has the same order than the private key. + std::vector<uint8> order; + if (!GetECKeyOrder(private_key, &order)) { + LOG(ERROR) << "Can't extract order parameter from EC private key"; + return false; + } + ScopedEC_GROUP group(EC_GROUP_new(EC_GFp_nist_method())); + if (!group.get()) { + LOG(ERROR) << "Can't create new EC_GROUP"; + return false; + } + if (!CopyBigNumFromBytes(order, &group.get()->order)) { + LOG(ERROR) << "Can't decode order from PrivateKey"; + return false; + } + EC_KEY_set_group(eckey.get(), group.release()); + + ScopedJavaGlobalRef<jobject> global_key; + global_key.Reset(NULL, private_key); + if (global_key.is_null()) { + LOG(ERROR) << "Can't create global JNI reference"; + return false; + } + ECDSA_set_ex_data(eckey.get(), + EcdsaGetExDataIndex(), + global_key.Release()); + + EVP_PKEY_assign_EC_KEY(pkey, eckey.release()); + return true; +} + +} // namespace + +EVP_PKEY* GetOpenSSLPrivateKeyWrapper(jobject private_key) { + // Create new empty EVP_PKEY instance. + ScopedEVP_PKEY pkey(EVP_PKEY_new()); + if (!pkey.get()) + return NULL; + + // Create sub key type, depending on private key's algorithm type. + PrivateKeyType key_type = GetPrivateKeyType(private_key); + switch (key_type) { + case PRIVATE_KEY_TYPE_RSA: + { + // Route around platform bug: if Android < 4.2, then + // base::android::RawSignDigestWithPrivateKey() cannot work, so + // instead, obtain a raw EVP_PKEY* to the system object + // backing this PrivateKey object. + const int kAndroid42ApiLevel = 17; + if (base::android::BuildInfo::GetInstance()->sdk_int() < + kAndroid42ApiLevel) { + EVP_PKEY* legacy_key = GetRsaLegacyKey(private_key); + if (legacy_key == NULL) + return NULL; + pkey.reset(legacy_key); + } else { + // Running on Android 4.2. + if (!GetRsaPkeyWrapper(private_key, pkey.get())) + return NULL; + } + } + break; + case PRIVATE_KEY_TYPE_DSA: + if (!GetDsaPkeyWrapper(private_key, pkey.get())) + return NULL; + break; + case PRIVATE_KEY_TYPE_ECDSA: + if (!GetEcdsaPkeyWrapper(private_key, pkey.get())) + return NULL; + break; + default: + LOG(WARNING) + << "GetOpenSSLPrivateKeyWrapper() called with invalid key type"; + return NULL; + } + return pkey.release(); +} + +} // namespace android +} // namespace net diff --git a/chromium/net/android/keystore_openssl.h b/chromium/net/android/keystore_openssl.h new file mode 100644 index 00000000000..ceb900c4be6 --- /dev/null +++ b/chromium/net/android/keystore_openssl.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef NET_ANDROID_KEYSTORE_OPENSSL_H +#define NET_ANDROID_KEYSTORE_OPENSSL_H + +#include <jni.h> +#include <openssl/evp.h> + +#include "net/base/net_export.h" + +// OpenSSL-specific functions to use the Android platform keystore. +// The features provided here are highly specific to OpenSSL and are +// segregated from net/android/keystore.h because the latter only provides +// simply JNI stubs to call Java code which only uses platform APIs. + +namespace net { +namespace android { + +// Create a custom OpenSSL EVP_PKEY instance that wraps a platform +// java.security.PrivateKey object, and will call the platform APIs +// through JNI to implement signing (and only signing). +// +// This method can be called from any thread. It shall only be used +// to implement client certificate handling though. +// +// |private_key| is a JNI local (or global) reference to the Java +// PrivateKey object. +// +// Returns a new EVP_PKEY* object with the following features: +// +// - Only contains a private key. +// +// - Owns its own _global_ JNI reference to the object. This means the +// caller can free |private_key| safely after the call, and that the +// the returned EVP_PKEY instance can be used from any thread. +// +// - Uses a custom method to implement the minimum functions required to +// *sign* the digest that is part of the "Verify Certificate" message +// during the OpenSSL handshake. Anything else will result in undefined +// behaviour. +NET_EXPORT EVP_PKEY* GetOpenSSLPrivateKeyWrapper(jobject private_key); + +} // namespace android +} // namespace net + +#endif // NET_ANDROID_KEYSTORE_OPENSSL_H diff --git a/chromium/net/android/keystore_unittest.cc b/chromium/net/android/keystore_unittest.cc new file mode 100644 index 00000000000..98944e29bd0 --- /dev/null +++ b/chromium/net/android/keystore_unittest.cc @@ -0,0 +1,725 @@ +// 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. + +#include <openssl/bn.h> +#include <openssl/dsa.h> +#include <openssl/ecdsa.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> + +#include "base/android/build_info.h" +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_handle.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "crypto/openssl_util.h" +#include "jni/AndroidKeyStoreTestUtil_jni.h" +#include "net/android/keystore.h" +#include "net/android/keystore_openssl.h" +#include "net/base/test_data_directory.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Technical note: +// +// This source file not only checks that signing with +// RawSignDigestWithPrivateKey() works correctly, it also verifies that +// the generated signature matches 100% of what OpenSSL generates when +// calling RSA_sign(NID_md5_sha1,...), DSA_sign(0, ...) or +// ECDSA_sign(0, ...). +// +// That's crucial to ensure that this function can later be used to +// implement client certificate support. More specifically, that it is +// possible to create a custom EVP_PKEY that uses +// RawSignDigestWithPrivateKey() internally to perform RSA/DSA/ECDSA +// signing, as invoked by the OpenSSL code at +// openssl/ssl/s3_clnt.c:ssl3_send_client_verify(). +// +// For more details, read the comments in AndroidKeyStore.java. +// +// Finally, it also checks that using the EVP_PKEY generated with +// GetOpenSSLPrivateKeyWrapper() works correctly. + +namespace net { +namespace android { + +namespace { + +typedef crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ScopedEVP_PKEY; +typedef crypto::ScopedOpenSSL<RSA, RSA_free> ScopedRSA; +typedef crypto::ScopedOpenSSL<DSA, DSA_free> ScopedDSA; +typedef crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ScopedEC_KEY; +typedef crypto::ScopedOpenSSL<BIGNUM, BN_free> ScopedBIGNUM; + +typedef crypto::ScopedOpenSSL< + PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free> + ScopedPKCS8_PRIV_KEY_INFO; + +typedef base::android::ScopedJavaLocalRef<jobject> ScopedJava; + +JNIEnv* InitEnv() { + JNIEnv* env = base::android::AttachCurrentThread(); + static bool inited = false; + if (!inited) { + RegisterNativesImpl(env); + inited = true; + } + return env; +} + +// Returns true if running on an Android version older than 4.2 +bool IsOnAndroidOlderThan_4_2(void) { + const int kAndroid42ApiLevel = 17; + int level = base::android::BuildInfo::GetInstance()->sdk_int(); + return level < kAndroid42ApiLevel; +} + +// Implements the callback expected by ERR_print_errors_cb(). +// used by GetOpenSSLErrorString below. +int openssl_print_error_callback(const char* msg, size_t msglen, void* u) { + std::string* result = reinterpret_cast<std::string*>(u); + result->append(msg, msglen); + return 1; +} + +// Retrieves the OpenSSL error as a string +std::string GetOpenSSLErrorString(void) { + std::string result; + ERR_print_errors_cb(openssl_print_error_callback, &result); + return result; +} + +// Resize a string to |size| bytes of data, then return its data buffer +// address cast as an 'unsigned char*', as expected by OpenSSL functions. +// |str| the target string. +// |size| the number of bytes to write into the string. +// Return the string's new buffer in memory, as an 'unsigned char*' +// pointer. +unsigned char* OpenSSLWriteInto(std::string* str, size_t size) { + return reinterpret_cast<unsigned char*>(WriteInto(str, size + 1)); +} + +// Load a given private key file into an EVP_PKEY. +// |filename| is the key file path. +// Returns a new EVP_PKEY on success, NULL on failure. +EVP_PKEY* ImportPrivateKeyFile(const char* filename) { + // Load file in memory. + base::FilePath certs_dir = GetTestCertsDirectory(); + base::FilePath file_path = certs_dir.AppendASCII(filename); + ScopedStdioHandle handle( + file_util::OpenFile(file_path, "rb")); + if (!handle.get()) { + LOG(ERROR) << "Could not open private key file: " << filename; + return NULL; + } + // Assume it is PEM_encoded. Load it as an EVP_PKEY. + EVP_PKEY* pkey = PEM_read_PrivateKey(handle.get(), NULL, NULL, NULL); + if (!pkey) { + LOG(ERROR) << "Could not load public key file: " << filename + << ", " << GetOpenSSLErrorString(); + return NULL; + } + return pkey; +} + +// Convert a private key into its PKCS#8 encoded representation. +// |pkey| is the EVP_PKEY handle for the private key. +// |pkcs8| will receive the PKCS#8 bytes. +// Returns true on success, false otherwise. +bool GetPrivateKeyPkcs8Bytes(const ScopedEVP_PKEY& pkey, + std::string* pkcs8) { + // Convert to PKCS#8 object. + ScopedPKCS8_PRIV_KEY_INFO p8_info(EVP_PKEY2PKCS8(pkey.get())); + if (!p8_info.get()) { + LOG(ERROR) << "Can't get PKCS#8 private key from EVP_PKEY: " + << GetOpenSSLErrorString(); + return false; + } + + // Then convert it + int len = i2d_PKCS8_PRIV_KEY_INFO(p8_info.get(), NULL); + unsigned char* p = OpenSSLWriteInto(pkcs8, static_cast<size_t>(len)); + i2d_PKCS8_PRIV_KEY_INFO(p8_info.get(), &p); + return true; +} + +bool ImportPrivateKeyFileAsPkcs8(const char* filename, + std::string* pkcs8) { + ScopedEVP_PKEY pkey(ImportPrivateKeyFile(filename)); + if (!pkey.get()) + return false; + return GetPrivateKeyPkcs8Bytes(pkey, pkcs8); +} + +// Same as ImportPrivateKey, but for public ones. +EVP_PKEY* ImportPublicKeyFile(const char* filename) { + // Load file as PEM data. + base::FilePath certs_dir = GetTestCertsDirectory(); + base::FilePath file_path = certs_dir.AppendASCII(filename); + ScopedStdioHandle handle(file_util::OpenFile(file_path, "rb")); + if (!handle.get()) { + LOG(ERROR) << "Could not open public key file: " << filename; + return NULL; + } + EVP_PKEY* pkey = PEM_read_PUBKEY(handle.get(), NULL, NULL, NULL); + if (!pkey) { + LOG(ERROR) << "Could not load public key file: " << filename + << ", " << GetOpenSSLErrorString(); + return NULL; + } + return pkey; +} + +// Retrieve a JNI local ref from encoded PKCS#8 data. +ScopedJava GetPKCS8PrivateKeyJava(PrivateKeyType key_type, + const std::string& pkcs8_key) { + JNIEnv* env = InitEnv(); + base::android::ScopedJavaLocalRef<jbyteArray> bytes( + base::android::ToJavaByteArray( + env, + reinterpret_cast<const uint8*>(pkcs8_key.data()), + pkcs8_key.size())); + + ScopedJava key( + Java_AndroidKeyStoreTestUtil_createPrivateKeyFromPKCS8( + env, key_type, bytes.obj())); + + return key; +} + +const char kTestRsaKeyFile[] = "android-test-key-rsa.pem"; + +// The RSA test hash must be 36 bytes exactly. +const char kTestRsaHash[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +// Retrieve a JNI local ref for our test RSA key. +ScopedJava GetRSATestKeyJava() { + std::string key; + if (!ImportPrivateKeyFileAsPkcs8(kTestRsaKeyFile, &key)) + return ScopedJava(); + return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_RSA, key); +} + +const char kTestDsaKeyFile[] = "android-test-key-dsa.pem"; +const char kTestDsaPublicKeyFile[] = "android-test-key-dsa-public.pem"; + +// The DSA test hash must be 20 bytes exactly. +const char kTestDsaHash[] = "0123456789ABCDEFGHIJ"; + +// Retrieve a JNI local ref for our test DSA key. +ScopedJava GetDSATestKeyJava() { + std::string key; + if (!ImportPrivateKeyFileAsPkcs8(kTestDsaKeyFile, &key)) + return ScopedJava(); + return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_DSA, key); +} + +// Call this function to verify that one message signed with our +// test DSA private key is correct. Since DSA signing introduces +// random elements in the signature, it is not possible to compare +// signature bits directly. However, one can use the public key +// to do the check. +bool VerifyTestDSASignature(const base::StringPiece& message, + const base::StringPiece& signature) { + ScopedEVP_PKEY pkey(ImportPublicKeyFile(kTestDsaPublicKeyFile)); + if (!pkey.get()) + return false; + + ScopedDSA pub_key(EVP_PKEY_get1_DSA(pkey.get())); + if (!pub_key.get()) { + LOG(ERROR) << "Could not get DSA public key: " + << GetOpenSSLErrorString(); + return false; + } + + const unsigned char* digest = + reinterpret_cast<const unsigned char*>(message.data()); + int digest_len = static_cast<int>(message.size()); + const unsigned char* sigbuf = + reinterpret_cast<const unsigned char*>(signature.data()); + int siglen = static_cast<int>(signature.size()); + + int ret = DSA_verify( + 0, digest, digest_len, sigbuf, siglen, pub_key.get()); + if (ret != 1) { + LOG(ERROR) << "DSA_verify() failed: " << GetOpenSSLErrorString(); + return false; + } + return true; +} + +const char kTestEcdsaKeyFile[] = "android-test-key-ecdsa.pem"; +const char kTestEcdsaPublicKeyFile[] = "android-test-key-ecdsa-public.pem"; + +// The test hash for ECDSA keys must be 20 bytes exactly. +const char kTestEcdsaHash[] = "0123456789ABCDEFGHIJ"; + +// Retrieve a JNI local ref for our test ECDSA key. +ScopedJava GetECDSATestKeyJava() { + std::string key; + if (!ImportPrivateKeyFileAsPkcs8(kTestEcdsaKeyFile, &key)) + return ScopedJava(); + return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_ECDSA, key); +} + +// Call this function to verify that one message signed with our +// test DSA private key is correct. Since DSA signing introduces +// random elements in the signature, it is not possible to compare +// signature bits directly. However, one can use the public key +// to do the check. +bool VerifyTestECDSASignature(const base::StringPiece& message, + const base::StringPiece& signature) { + ScopedEVP_PKEY pkey(ImportPublicKeyFile(kTestEcdsaPublicKeyFile)); + if (!pkey.get()) + return false; + ScopedEC_KEY pub_key(EVP_PKEY_get1_EC_KEY(pkey.get())); + if (!pub_key.get()) { + LOG(ERROR) << "Could not get ECDSA public key: " + << GetOpenSSLErrorString(); + return false; + } + + const unsigned char* digest = + reinterpret_cast<const unsigned char*>(message.data()); + int digest_len = static_cast<int>(message.size()); + const unsigned char* sigbuf = + reinterpret_cast<const unsigned char*>(signature.data()); + int siglen = static_cast<int>(signature.size()); + + int ret = ECDSA_verify( + 0, digest, digest_len, sigbuf, siglen, pub_key.get()); + if (ret != 1) { + LOG(ERROR) << "ECDSA_verify() failed: " << GetOpenSSLErrorString(); + return false; + } + return true; +} + +// Sign a message with OpenSSL, return the result as a string. +// |message| is the message to be signed. +// |openssl_key| is an OpenSSL EVP_PKEY to use. +// |result| receives the result. +// Returns true on success, false otherwise. +bool SignWithOpenSSL(const base::StringPiece& message, + EVP_PKEY* openssl_key, + std::string* result) { + const unsigned char* digest = + reinterpret_cast<const unsigned char*>(message.data()); + unsigned int digest_len = static_cast<unsigned int>(message.size()); + std::string signature; + size_t signature_size; + size_t max_signature_size; + int key_type = EVP_PKEY_id(openssl_key); + switch (key_type) { + case EVP_PKEY_RSA: + { + ScopedRSA rsa(EVP_PKEY_get1_RSA(openssl_key)); + if (!rsa.get()) { + LOG(ERROR) << "Could not get RSA from EVP_PKEY: " + << GetOpenSSLErrorString(); + return false; + } + // With RSA, the signature will always be RSA_size() bytes. + max_signature_size = static_cast<size_t>(RSA_size(rsa.get())); + unsigned char* p = OpenSSLWriteInto(&signature, + max_signature_size); + unsigned int p_len = 0; + int ret = RSA_sign( + NID_md5_sha1, digest, digest_len, p, &p_len, rsa.get()); + if (ret != 1) { + LOG(ERROR) << "RSA_sign() failed: " << GetOpenSSLErrorString(); + return false; + } + signature_size = static_cast<size_t>(p_len); + break; + } + case EVP_PKEY_DSA: + { + ScopedDSA dsa(EVP_PKEY_get1_DSA(openssl_key)); + if (!dsa.get()) { + LOG(ERROR) << "Could not get DSA from EVP_PKEY: " + << GetOpenSSLErrorString(); + return false; + } + // Note, the actual signature can be smaller than DSA_size() + max_signature_size = static_cast<size_t>(DSA_size(dsa.get())); + unsigned char* p = OpenSSLWriteInto(&signature, + max_signature_size); + unsigned int p_len = 0; + // Note: first parameter is ignored by function. + int ret = DSA_sign(0, digest, digest_len, p, &p_len, dsa.get()); + if (ret != 1) { + LOG(ERROR) << "DSA_sign() failed: " << GetOpenSSLErrorString(); + return false; + } + signature_size = static_cast<size_t>(p_len); + break; + } + case EVP_PKEY_EC: + { + ScopedEC_KEY ecdsa(EVP_PKEY_get1_EC_KEY(openssl_key)); + if (!ecdsa.get()) { + LOG(ERROR) << "Could not get EC_KEY from EVP_PKEY: " + << GetOpenSSLErrorString(); + return false; + } + // Note, the actual signature can be smaller than ECDSA_size() + max_signature_size = ECDSA_size(ecdsa.get()); + unsigned char* p = OpenSSLWriteInto(&signature, + max_signature_size); + unsigned int p_len = 0; + // Note: first parameter is ignored by function. + int ret = ECDSA_sign( + 0, digest, digest_len, p, &p_len, ecdsa.get()); + if (ret != 1) { + LOG(ERROR) << "ECDSA_sign() fialed: " << GetOpenSSLErrorString(); + return false; + } + signature_size = static_cast<size_t>(p_len); + break; + } + default: + LOG(WARNING) << "Invalid OpenSSL key type: " << key_type; + return false; + } + + if (signature_size == 0) { + LOG(ERROR) << "Signature is empty!"; + return false; + } + if (signature_size > max_signature_size) { + LOG(ERROR) << "Signature size mismatch, actual " << signature_size + << ", expected <= " << max_signature_size; + return false; + } + signature.resize(signature_size); + result->swap(signature); + return true; +} + +// Check that a generated signature for a given message matches +// OpenSSL output byte-by-byte. +// |message| is the input message. +// |signature| is the generated signature for the message. +// |openssl_key| is a raw EVP_PKEY for the same private key than the +// one which was used to generate the signature. +// Returns true on success, false otherwise. +bool CompareSignatureWithOpenSSL(const base::StringPiece& message, + const base::StringPiece& signature, + EVP_PKEY* openssl_key) { + std::string openssl_signature; + SignWithOpenSSL(message, openssl_key, &openssl_signature); + + if (signature.size() != openssl_signature.size()) { + LOG(ERROR) << "Signature size mismatch, actual " + << signature.size() << ", expected " + << openssl_signature.size(); + return false; + } + for (size_t n = 0; n < signature.size(); ++n) { + if (openssl_signature[n] != signature[n]) { + LOG(ERROR) << "Signature byte mismatch at index " << n + << "actual " << signature[n] << ", expected " + << openssl_signature[n]; + LOG(ERROR) << "Actual signature : " + << base::HexEncode(signature.data(), signature.size()); + LOG(ERROR) << "Expected signature: " + << base::HexEncode(openssl_signature.data(), + openssl_signature.size()); + return false; + } + } + return true; +} + +// Sign a message with our platform API. +// +// |android_key| is a JNI reference to the platform PrivateKey object. +// |openssl_key| is a pointer to an OpenSSL key object for the exact +// same key content. +// |message| is a message. +// |result| will receive the result. +void DoKeySigning(jobject android_key, + EVP_PKEY* openssl_key, + const base::StringPiece& message, + std::string* result) { + // First, get the platform signature. + std::vector<uint8> android_signature; + ASSERT_TRUE( + RawSignDigestWithPrivateKey(android_key, + message, + &android_signature)); + + result->assign( + reinterpret_cast<const char*>(&android_signature[0]), + android_signature.size()); +} + +// Sign a message with our OpenSSL EVP_PKEY wrapper around platform +// APIS. +// +// |android_key| is a JNI reference to the platform PrivateKey object. +// |openssl_key| is a pointer to an OpenSSL key object for the exact +// same key content. +// |message| is a message. +// |result| will receive the result. +void DoKeySigningWithWrapper(EVP_PKEY* wrapper_key, + EVP_PKEY* openssl_key, + const base::StringPiece& message, + std::string* result) { + // First, get the platform signature. + std::string wrapper_signature; + SignWithOpenSSL(message, wrapper_key, &wrapper_signature); + ASSERT_NE(0U, wrapper_signature.size()); + + result->assign( + reinterpret_cast<const char*>(&wrapper_signature[0]), + wrapper_signature.size()); +} + +} // namespace + +TEST(AndroidKeyStore,GetRSAKeyModulus) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + InitEnv(); + + // Load the test RSA key. + ScopedEVP_PKEY pkey(ImportPrivateKeyFile(kTestRsaKeyFile)); + ASSERT_TRUE(pkey.get()); + + // Convert it to encoded PKCS#8 bytes. + std::string pkcs8_data; + ASSERT_TRUE(GetPrivateKeyPkcs8Bytes(pkey, &pkcs8_data)); + + // Create platform PrivateKey object from it. + ScopedJava key_java = GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_RSA, + pkcs8_data); + ASSERT_FALSE(key_java.is_null()); + + // Retrieve the corresponding modulus through JNI + std::vector<uint8> modulus_java; + ASSERT_TRUE(GetRSAKeyModulus(key_java.obj(), &modulus_java)); + + // Create an OpenSSL BIGNUM from it. + ScopedBIGNUM bn( + BN_bin2bn( + reinterpret_cast<const unsigned char*>(&modulus_java[0]), + static_cast<int>(modulus_java.size()), + NULL)); + ASSERT_TRUE(bn.get()); + + // Compare it to the one in the RSA key, they must be identical. + ScopedRSA rsa(EVP_PKEY_get1_RSA(pkey.get())); + ASSERT_TRUE(rsa.get()) << GetOpenSSLErrorString(); + + ASSERT_EQ(0, BN_cmp(bn.get(), rsa.get()->n)); +} + +TEST(AndroidKeyStore,GetDSAKeyParamQ) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + InitEnv(); + + // Load the test DSA key. + ScopedEVP_PKEY pkey(ImportPrivateKeyFile(kTestDsaKeyFile)); + ASSERT_TRUE(pkey.get()); + + // Convert it to encoded PKCS#8 bytes. + std::string pkcs8_data; + ASSERT_TRUE(GetPrivateKeyPkcs8Bytes(pkey, &pkcs8_data)); + + // Create platform PrivateKey object from it. + ScopedJava key_java = GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_DSA, + pkcs8_data); + ASSERT_FALSE(key_java.is_null()); + + // Retrieve the corresponding Q parameter through JNI + std::vector<uint8> q_java; + ASSERT_TRUE(GetDSAKeyParamQ(key_java.obj(), &q_java)); + + // Create an OpenSSL BIGNUM from it. + ScopedBIGNUM bn( + BN_bin2bn( + reinterpret_cast<const unsigned char*>(&q_java[0]), + static_cast<int>(q_java.size()), + NULL)); + ASSERT_TRUE(bn.get()); + + // Compare it to the one in the RSA key, they must be identical. + ScopedDSA dsa(EVP_PKEY_get1_DSA(pkey.get())); + ASSERT_TRUE(dsa.get()) << GetOpenSSLErrorString(); + + ASSERT_EQ(0, BN_cmp(bn.get(), dsa.get()->q)); +} + +TEST(AndroidKeyStore,GetPrivateKeyTypeRSA) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + + ScopedJava rsa_key = GetRSATestKeyJava(); + ASSERT_FALSE(rsa_key.is_null()); + EXPECT_EQ(PRIVATE_KEY_TYPE_RSA, + GetPrivateKeyType(rsa_key.obj())); +} + +TEST(AndroidKeyStore,SignWithPrivateKeyRSA) { + ScopedJava rsa_key = GetRSATestKeyJava(); + ASSERT_FALSE(rsa_key.is_null()); + + if (IsOnAndroidOlderThan_4_2()) { + LOG(INFO) << "This test can't run on Android < 4.2"; + return; + } + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestRsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + std::string message = kTestRsaHash; + ASSERT_EQ(36U, message.size()); + + std::string signature; + DoKeySigning(rsa_key.obj(), openssl_key.get(), message, &signature); + ASSERT_TRUE( + CompareSignatureWithOpenSSL(message, signature, openssl_key.get())); + // All good. +} + +TEST(AndroidKeyStore,SignWithWrapperKeyRSA) { + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + ScopedJava rsa_key = GetRSATestKeyJava(); + ASSERT_FALSE(rsa_key.is_null()); + + ScopedEVP_PKEY wrapper_key(GetOpenSSLPrivateKeyWrapper(rsa_key.obj())); + ASSERT_TRUE(wrapper_key.get() != NULL); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestRsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + // Check that RSA_size() works properly on the wrapper key. + EXPECT_EQ(EVP_PKEY_size(openssl_key.get()), + EVP_PKEY_size(wrapper_key.get())); + + // Message size must be 36 for RSA_sign(NID_md5_sha1,...) to return + // without an error. + std::string message = kTestRsaHash; + ASSERT_EQ(36U, message.size()); + + std::string signature; + DoKeySigningWithWrapper(wrapper_key.get(), + openssl_key.get(), + message, + &signature); + ASSERT_TRUE( + CompareSignatureWithOpenSSL(message, signature, openssl_key.get())); +} + +TEST(AndroidKeyStore,GetPrivateKeyTypeDSA) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + + ScopedJava dsa_key = GetDSATestKeyJava(); + ASSERT_FALSE(dsa_key.is_null()); + EXPECT_EQ(PRIVATE_KEY_TYPE_DSA, + GetPrivateKeyType(dsa_key.obj())); +} + +TEST(AndroidKeyStore,SignWithPrivateKeyDSA) { + ScopedJava dsa_key = GetDSATestKeyJava(); + ASSERT_FALSE(dsa_key.is_null()); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestDsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + std::string message = kTestDsaHash; + ASSERT_EQ(20U, message.size()); + + std::string signature; + DoKeySigning(dsa_key.obj(), openssl_key.get(), message, &signature); + ASSERT_TRUE(VerifyTestDSASignature(message, signature)); +} + +TEST(AndroidKeyStore,SignWithWrapperKeyDSA) { + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + ScopedJava dsa_key = GetDSATestKeyJava(); + ASSERT_FALSE(dsa_key.is_null()); + + ScopedEVP_PKEY wrapper_key( + GetOpenSSLPrivateKeyWrapper(dsa_key.obj())); + ASSERT_TRUE(wrapper_key.get()); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestDsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + // Check that DSA_size() works correctly on the wrapper. + EXPECT_EQ(EVP_PKEY_size(openssl_key.get()), + EVP_PKEY_size(wrapper_key.get())); + + std::string message = kTestDsaHash; + std::string signature; + DoKeySigningWithWrapper(wrapper_key.get(), + openssl_key.get(), + message, + &signature); + ASSERT_TRUE(VerifyTestDSASignature(message, signature)); +} + +TEST(AndroidKeyStore,GetPrivateKeyTypeECDSA) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + + ScopedJava ecdsa_key = GetECDSATestKeyJava(); + ASSERT_FALSE(ecdsa_key.is_null()); + EXPECT_EQ(PRIVATE_KEY_TYPE_ECDSA, + GetPrivateKeyType(ecdsa_key.obj())); +} + +TEST(AndroidKeyStore,SignWithPrivateKeyECDSA) { + ScopedJava ecdsa_key = GetECDSATestKeyJava(); + ASSERT_FALSE(ecdsa_key.is_null()); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestEcdsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + std::string message = kTestEcdsaHash; + std::string signature; + DoKeySigning(ecdsa_key.obj(), openssl_key.get(), message, &signature); + ASSERT_TRUE(VerifyTestECDSASignature(message, signature)); +} + +TEST(AndroidKeyStore, SignWithWrapperKeyECDSA) { + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + ScopedJava ecdsa_key = GetECDSATestKeyJava(); + ASSERT_FALSE(ecdsa_key.is_null()); + + ScopedEVP_PKEY wrapper_key( + GetOpenSSLPrivateKeyWrapper(ecdsa_key.obj())); + ASSERT_TRUE(wrapper_key.get()); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestEcdsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + // Check that ECDSA size works correctly on the wrapper. + EXPECT_EQ(EVP_PKEY_size(openssl_key.get()), + EVP_PKEY_size(wrapper_key.get())); + + std::string message = kTestEcdsaHash; + std::string signature; + DoKeySigningWithWrapper(wrapper_key.get(), + openssl_key.get(), + message, + &signature); + ASSERT_TRUE(VerifyTestECDSASignature(message, signature)); +} + +} // namespace android +} // namespace net diff --git a/chromium/net/android/net_jni_registrar.cc b/chromium/net/android/net_jni_registrar.cc new file mode 100644 index 00000000000..a6e09b65080 --- /dev/null +++ b/chromium/net/android/net_jni_registrar.cc @@ -0,0 +1,34 @@ +// 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. + +#include "net/android/net_jni_registrar.h" + +#include "base/basictypes.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "net/android/gurl_utils.h" +#include "net/android/keystore.h" +#include "net/android/network_change_notifier_android.h" +#include "net/android/network_library.h" +#include "net/proxy/proxy_config_service_android.h" + +namespace net { +namespace android { + +static base::android::RegistrationMethod kNetRegisteredMethods[] = { + { "AndroidKeyStore", net::android::RegisterKeyStore }, + { "AndroidNetworkLibrary", net::android::RegisterNetworkLibrary }, + { "GURLUtils", net::RegisterGURLUtils }, + { "NetworkChangeNotifierAndroid", + net::NetworkChangeNotifierAndroid::Register }, + { "ProxyConfigService", net::ProxyConfigServiceAndroid::Register }, +}; + +bool RegisterJni(JNIEnv* env) { + return base::android::RegisterNativeMethods( + env, kNetRegisteredMethods, arraysize(kNetRegisteredMethods)); +} + +} // namespace android +} // namespace net diff --git a/chromium/net/android/net_jni_registrar.h b/chromium/net/android/net_jni_registrar.h new file mode 100644 index 00000000000..2b45fb26d07 --- /dev/null +++ b/chromium/net/android/net_jni_registrar.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef NET_ANDROID_NET_JNI_REGISTRAR_H_ +#define NET_ANDROID_NET_JNI_REGISTRAR_H_ + +#include <jni.h> + +#include "net/base/net_export.h" + +namespace net { +namespace android { + +// Register all JNI bindings necessary for net. +NET_EXPORT bool RegisterJni(JNIEnv* env); + +} // namespace android +} // namespace net + +#endif // NET_ANDROID_NET_JNI_REGISTRAR_H_ diff --git a/chromium/net/android/network_change_notifier_android.cc b/chromium/net/android/network_change_notifier_android.cc new file mode 100644 index 00000000000..d4e1a5c8524 --- /dev/null +++ b/chromium/net/android/network_change_notifier_android.cc @@ -0,0 +1,104 @@ +// 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. + +//////////////////////////////////////////////////////////////////////////////// +// Threading considerations: +// +// This class is designed to meet various threading guarantees starting from the +// ones imposed by NetworkChangeNotifier: +// - The notifier can be constructed on any thread. +// - GetCurrentConnectionType() can be called on any thread. +// +// The fact that this implementation of NetworkChangeNotifier is backed by a +// Java side singleton class (see NetworkChangeNotifier.java) adds another +// threading constraint: +// - The calls to the Java side (stateful) object must be performed from a +// single thread. This object happens to be a singleton which is used on the +// application side on the main thread. Therefore all the method calls from +// the native NetworkChangeNotifierAndroid class to its Java counterpart are +// performed on the main thread. +// +// This leads to a design involving the following native classes: +// 1) NetworkChangeNotifierFactoryAndroid ('factory') +// 2) NetworkChangeNotifierDelegateAndroid ('delegate') +// 3) NetworkChangeNotifierAndroid ('notifier') +// +// The factory constructs and owns the delegate. The factory is constructed and +// destroyed on the main thread which makes it construct and destroy the +// delegate on the main thread too. This guarantees that the calls to the Java +// side are performed on the main thread. +// Note that after the factory's construction, the factory's creation method can +// be called from any thread since the delegate's construction (performing the +// JNI calls) already happened on the main thread (when the factory was +// constructed). +// +//////////////////////////////////////////////////////////////////////////////// +// Propagation of network change notifications: +// +// When the factory is requested to create a new instance of the notifier, the +// factory passes the delegate to the notifier (without transferring ownership). +// Note that there is a one-to-one mapping between the factory and the +// delegate as explained above. But the factory naturally creates multiple +// instances of the notifier. That means that there is a one-to-many mapping +// between delegate and notifier (i.e. a single delegate can be shared by +// multiple notifiers). +// At construction the notifier (which is also an observer) subscribes to +// notifications fired by the delegate. These notifications, received by the +// delegate (and forwarded to the notifier(s)), are sent by the Java side +// notifier (see NetworkChangeNotifier.java) and are initiated by the Android +// platform. +// Notifications from the Java side always arrive on the main thread. The +// delegate then forwards these notifications to the threads of each observer +// (network change notifier). The network change notifier than processes the +// state change, and notifies each of its observers on their threads. +// +// This can also be seen as: +// Android platform -> NetworkChangeNotifier (Java) -> +// NetworkChangeNotifierDelegateAndroid -> NetworkChangeNotifierAndroid. + +#include "net/android/network_change_notifier_android.h" + +namespace net { + +NetworkChangeNotifierAndroid::~NetworkChangeNotifierAndroid() { + delegate_->RemoveObserver(this); +} + +NetworkChangeNotifier::ConnectionType +NetworkChangeNotifierAndroid::GetCurrentConnectionType() const { + return delegate_->GetCurrentConnectionType(); +} + +void NetworkChangeNotifierAndroid::OnConnectionTypeChanged() { + NetworkChangeNotifier::NotifyObserversOfIPAddressChange(); + NetworkChangeNotifier::NotifyObserversOfConnectionTypeChange(); +} + +// static +bool NetworkChangeNotifierAndroid::Register(JNIEnv* env) { + return NetworkChangeNotifierDelegateAndroid::Register(env); +} + +NetworkChangeNotifierAndroid::NetworkChangeNotifierAndroid( + NetworkChangeNotifierDelegateAndroid* delegate) + : NetworkChangeNotifier(NetworkChangeCalculatorParamsAndroid()), + delegate_(delegate) { + delegate_->AddObserver(this); +} + +// static +NetworkChangeNotifier::NetworkChangeCalculatorParams +NetworkChangeNotifierAndroid::NetworkChangeCalculatorParamsAndroid() { + NetworkChangeCalculatorParams params; + // IPAddressChanged is produced immediately prior to ConnectionTypeChanged + // so delay IPAddressChanged so they get merged with the following + // ConnectionTypeChanged signal. + params.ip_address_offline_delay_ = base::TimeDelta::FromSeconds(1); + params.ip_address_online_delay_ = base::TimeDelta::FromSeconds(1); + params.connection_type_offline_delay_ = base::TimeDelta::FromSeconds(0); + params.connection_type_online_delay_ = base::TimeDelta::FromSeconds(0); + return params; +} + +} // namespace net diff --git a/chromium/net/android/network_change_notifier_android.h b/chromium/net/android/network_change_notifier_android.h new file mode 100644 index 00000000000..f167cfc283b --- /dev/null +++ b/chromium/net/android/network_change_notifier_android.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef NET_ANDROID_NETWORK_CHANGE_NOTIFIER_ANDROID_H_ +#define NET_ANDROID_NETWORK_CHANGE_NOTIFIER_ANDROID_H_ + +#include "base/android/jni_android.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "net/android/network_change_notifier_delegate_android.h" +#include "net/base/network_change_notifier.h" + +namespace net { + +class NetworkChangeNotifierAndroidTest; +class NetworkChangeNotifierFactoryAndroid; + +// NetworkChangeNotifierAndroid observes network events from the Android +// notification system and forwards them to observers. +// +// The implementation is complicated by the differing lifetime and thread +// affinity requirements of Android notifications and of NetworkChangeNotifier. +// +// High-level overview: +// NetworkChangeNotifier.java - Receives notifications from Android system, and +// notifies native code via JNI (on the main application thread). +// NetworkChangeNotifierDelegateAndroid ('Delegate') - Listens for notifications +// sent via JNI on the main application thread, and forwards them to observers +// on their threads. Owned by Factory, lives exclusively on main application +// thread. +// NetworkChangeNotifierFactoryAndroid ('Factory') - Creates the Delegate on the +// main thread to receive JNI events, and vends Notifiers. Lives exclusively +// on main application thread, and outlives all other classes. +// NetworkChangeNotifierAndroid ('Notifier') - Receives event notifications from +// the Delegate. Processes and forwards these events to the +// NetworkChangeNotifier observers on their threads. May live on any thread +// and be called by any thread. +// +// For more details, see the implementation file. +class NET_EXPORT_PRIVATE NetworkChangeNotifierAndroid + : public NetworkChangeNotifier, + public NetworkChangeNotifierDelegateAndroid::Observer { + public: + virtual ~NetworkChangeNotifierAndroid(); + + // NetworkChangeNotifier: + virtual ConnectionType GetCurrentConnectionType() const OVERRIDE; + + // NetworkChangeNotifierDelegateAndroid::Observer: + virtual void OnConnectionTypeChanged() OVERRIDE; + + static bool Register(JNIEnv* env); + + private: + friend class NetworkChangeNotifierAndroidTest; + friend class NetworkChangeNotifierFactoryAndroid; + + explicit NetworkChangeNotifierAndroid( + NetworkChangeNotifierDelegateAndroid* delegate); + + static NetworkChangeCalculatorParams NetworkChangeCalculatorParamsAndroid(); + + NetworkChangeNotifierDelegateAndroid* const delegate_; + + DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierAndroid); +}; + +} // namespace net + +#endif // NET_ANDROID_NETWORK_CHANGE_NOTIFIER_ANDROID_H_ diff --git a/chromium/net/android/network_change_notifier_android_unittest.cc b/chromium/net/android/network_change_notifier_android_unittest.cc new file mode 100644 index 00000000000..6ab894cef64 --- /dev/null +++ b/chromium/net/android/network_change_notifier_android_unittest.cc @@ -0,0 +1,216 @@ +// 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. + +// See network_change_notifier_android.h for design explanations. + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "net/android/network_change_notifier_android.h" +#include "net/android/network_change_notifier_delegate_android.h" +#include "net/base/network_change_notifier.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class NetworkChangeNotifierDelegateAndroidObserver + : public NetworkChangeNotifierDelegateAndroid::Observer { + public: + NetworkChangeNotifierDelegateAndroidObserver() : notifications_count_(0) {} + + // NetworkChangeNotifierDelegateAndroid::Observer: + virtual void OnConnectionTypeChanged() OVERRIDE { + notifications_count_++; + } + + int notifications_count() const { + return notifications_count_; + } + + private: + int notifications_count_; +}; + +class NetworkChangeNotifierObserver + : public NetworkChangeNotifier::ConnectionTypeObserver { + public: + NetworkChangeNotifierObserver() : notifications_count_(0) {} + + // NetworkChangeNotifier::Observer: + virtual void OnConnectionTypeChanged( + NetworkChangeNotifier::ConnectionType connection_type) OVERRIDE { + notifications_count_++; + } + + int notifications_count() const { + return notifications_count_; + } + + private: + int notifications_count_; +}; + +} // namespace + +class BaseNetworkChangeNotifierAndroidTest : public testing::Test { + protected: + typedef NetworkChangeNotifier::ConnectionType ConnectionType; + + virtual ~BaseNetworkChangeNotifierAndroidTest() {} + + void RunTest( + const base::Callback<int(void)>& notifications_count_getter, + const base::Callback<ConnectionType(void)>& connection_type_getter) { + EXPECT_EQ(0, notifications_count_getter.Run()); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN, + connection_type_getter.Run()); + + // Changing from online to offline should trigger a notification. + SetOffline(); + EXPECT_EQ(1, notifications_count_getter.Run()); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_NONE, + connection_type_getter.Run()); + + // No notification should be triggered when the offline state hasn't + // changed. + SetOffline(); + EXPECT_EQ(1, notifications_count_getter.Run()); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_NONE, + connection_type_getter.Run()); + + // Going from offline to online should trigger a notification. + SetOnline(); + EXPECT_EQ(2, notifications_count_getter.Run()); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN, + connection_type_getter.Run()); + } + + void SetOnline() { + delegate_.SetOnline(); + // Note that this is needed because ObserverListThreadSafe uses PostTask(). + base::MessageLoop::current()->RunUntilIdle(); + } + + void SetOffline() { + delegate_.SetOffline(); + // See comment above. + base::MessageLoop::current()->RunUntilIdle(); + } + + NetworkChangeNotifierDelegateAndroid delegate_; +}; + +// Tests that NetworkChangeNotifierDelegateAndroid is initialized with the +// actual connection type rather than a hardcoded one (e.g. +// CONNECTION_UNKNOWN). Initializing the connection type to CONNECTION_UNKNOWN +// and relying on the first network change notification to set it correctly can +// be problematic in case there is a long delay between the delegate's +// construction and the notification. +TEST_F(BaseNetworkChangeNotifierAndroidTest, + DelegateIsInitializedWithCurrentConnectionType) { + SetOffline(); + ASSERT_EQ(NetworkChangeNotifier::CONNECTION_NONE, + delegate_.GetCurrentConnectionType()); + // Instantiate another delegate to validate that it uses the actual + // connection type at construction. + scoped_ptr<NetworkChangeNotifierDelegateAndroid> other_delegate( + new NetworkChangeNotifierDelegateAndroid()); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_NONE, + other_delegate->GetCurrentConnectionType()); + + // Toggle the global connectivity state and instantiate another delegate + // again. + SetOnline(); + ASSERT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN, + delegate_.GetCurrentConnectionType()); + other_delegate.reset(new NetworkChangeNotifierDelegateAndroid()); + EXPECT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN, + other_delegate->GetCurrentConnectionType()); +} + +class NetworkChangeNotifierDelegateAndroidTest + : public BaseNetworkChangeNotifierAndroidTest { + protected: + NetworkChangeNotifierDelegateAndroidTest() { + delegate_.AddObserver(&delegate_observer_); + delegate_.AddObserver(&other_delegate_observer_); + } + + virtual ~NetworkChangeNotifierDelegateAndroidTest() { + delegate_.RemoveObserver(&delegate_observer_); + delegate_.RemoveObserver(&other_delegate_observer_); + } + + NetworkChangeNotifierDelegateAndroidObserver delegate_observer_; + NetworkChangeNotifierDelegateAndroidObserver other_delegate_observer_; +}; + +// Tests that the NetworkChangeNotifierDelegateAndroid's observers are notified. +// A testing-only observer is used here for testing. In production the +// delegate's observers are instances of NetworkChangeNotifierAndroid. +TEST_F(NetworkChangeNotifierDelegateAndroidTest, DelegateObserverNotified) { + // Test the logic with a single observer. + RunTest( + base::Bind( + &NetworkChangeNotifierDelegateAndroidObserver::notifications_count, + base::Unretained(&delegate_observer_)), + base::Bind( + &NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionType, + base::Unretained(&delegate_))); + // Check that *all* the observers are notified. Both observers should have the + // same state. + EXPECT_EQ(delegate_observer_.notifications_count(), + other_delegate_observer_.notifications_count()); +} + +class NetworkChangeNotifierAndroidTest + : public BaseNetworkChangeNotifierAndroidTest { + protected: + NetworkChangeNotifierAndroidTest() : notifier_(&delegate_) { + NetworkChangeNotifier::AddConnectionTypeObserver( + &connection_type_observer_); + NetworkChangeNotifier::AddConnectionTypeObserver( + &other_connection_type_observer_); + } + + NetworkChangeNotifierObserver connection_type_observer_; + NetworkChangeNotifierObserver other_connection_type_observer_; + NetworkChangeNotifier::DisableForTest disable_for_test_; + NetworkChangeNotifierAndroid notifier_; +}; + +// When a NetworkChangeNotifierAndroid is observing a +// NetworkChangeNotifierDelegateAndroid for network state changes, and the +// NetworkChangeNotifierDelegateAndroid's connectivity state changes, the +// NetworkChangeNotifierAndroid should reflect that state. +TEST_F(NetworkChangeNotifierAndroidTest, + NotificationsSentToNetworkChangeNotifierAndroid) { + RunTest( + base::Bind( + &NetworkChangeNotifierObserver::notifications_count, + base::Unretained(&connection_type_observer_)), + base::Bind( + &NetworkChangeNotifierAndroid::GetCurrentConnectionType, + base::Unretained(¬ifier_))); +} + +// When a NetworkChangeNotifierAndroid's connection state changes, it should +// notify all of its observers. +TEST_F(NetworkChangeNotifierAndroidTest, + NotificationsSentToClientsOfNetworkChangeNotifier) { + RunTest( + base::Bind( + &NetworkChangeNotifierObserver::notifications_count, + base::Unretained(&connection_type_observer_)), + base::Bind(&NetworkChangeNotifier::GetConnectionType)); + // Check that *all* the observers are notified. + EXPECT_EQ(connection_type_observer_.notifications_count(), + other_connection_type_observer_.notifications_count()); +} + +} // namespace net diff --git a/chromium/net/android/network_change_notifier_delegate_android.cc b/chromium/net/android/network_change_notifier_delegate_android.cc new file mode 100644 index 00000000000..0031260b73e --- /dev/null +++ b/chromium/net/android/network_change_notifier_delegate_android.cc @@ -0,0 +1,112 @@ +// 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. + +#include "net/android/network_change_notifier_delegate_android.h" + +#include "base/logging.h" +#include "jni/NetworkChangeNotifier_jni.h" + +namespace net { + +namespace { + +// Converts a Java side connection type (integer) to +// the native side NetworkChangeNotifier::ConnectionType. +NetworkChangeNotifier::ConnectionType ConvertConnectionType( + jint connection_type) { + switch (connection_type) { + case NetworkChangeNotifier::CONNECTION_UNKNOWN: + case NetworkChangeNotifier::CONNECTION_ETHERNET: + case NetworkChangeNotifier::CONNECTION_WIFI: + case NetworkChangeNotifier::CONNECTION_2G: + case NetworkChangeNotifier::CONNECTION_3G: + case NetworkChangeNotifier::CONNECTION_4G: + case NetworkChangeNotifier::CONNECTION_NONE: + break; + default: + NOTREACHED() << "Unknown connection type received: " << connection_type; + return NetworkChangeNotifier::CONNECTION_UNKNOWN; + } + return static_cast<NetworkChangeNotifier::ConnectionType>(connection_type); +} + +} // namespace + +NetworkChangeNotifierDelegateAndroid::NetworkChangeNotifierDelegateAndroid() + : observers_(new ObserverListThreadSafe<Observer>()) { + JNIEnv* env = base::android::AttachCurrentThread(); + java_network_change_notifier_.Reset( + Java_NetworkChangeNotifier_init( + env, base::android::GetApplicationContext())); + Java_NetworkChangeNotifier_addNativeObserver( + env, java_network_change_notifier_.obj(), reinterpret_cast<jint>(this)); + SetCurrentConnectionType( + ConvertConnectionType( + Java_NetworkChangeNotifier_getCurrentConnectionType( + env, java_network_change_notifier_.obj()))); +} + +NetworkChangeNotifierDelegateAndroid::~NetworkChangeNotifierDelegateAndroid() { + DCHECK(thread_checker_.CalledOnValidThread()); + observers_->AssertEmpty(); + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NetworkChangeNotifier_removeNativeObserver( + env, java_network_change_notifier_.obj(), reinterpret_cast<jint>(this)); +} + +NetworkChangeNotifier::ConnectionType +NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionType() const { + base::AutoLock auto_lock(connection_type_lock_); + return connection_type_; +} + +void NetworkChangeNotifierDelegateAndroid::NotifyConnectionTypeChanged( + JNIEnv* env, + jobject obj, + jint new_connection_type) { + DCHECK(thread_checker_.CalledOnValidThread()); + const ConnectionType actual_connection_type = ConvertConnectionType( + new_connection_type); + SetCurrentConnectionType(actual_connection_type); + observers_->Notify(&Observer::OnConnectionTypeChanged); +} + +jint NetworkChangeNotifierDelegateAndroid::GetConnectionType(JNIEnv*, + jobject) const { + DCHECK(thread_checker_.CalledOnValidThread()); + return GetCurrentConnectionType(); +} + +void NetworkChangeNotifierDelegateAndroid::AddObserver( + Observer* observer) { + observers_->AddObserver(observer); +} + +void NetworkChangeNotifierDelegateAndroid::RemoveObserver( + Observer* observer) { + observers_->RemoveObserver(observer); +} + +// static +bool NetworkChangeNotifierDelegateAndroid::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +void NetworkChangeNotifierDelegateAndroid::SetCurrentConnectionType( + ConnectionType new_connection_type) { + base::AutoLock auto_lock(connection_type_lock_); + connection_type_ = new_connection_type; +} + +void NetworkChangeNotifierDelegateAndroid::SetOnline() { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NetworkChangeNotifier_forceConnectivityState(env, true); +} + +void NetworkChangeNotifierDelegateAndroid::SetOffline() { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NetworkChangeNotifier_forceConnectivityState(env, false); +} + +} // namespace net diff --git a/chromium/net/android/network_change_notifier_delegate_android.h b/chromium/net/android/network_change_notifier_delegate_android.h new file mode 100644 index 00000000000..f93c30b0b25 --- /dev/null +++ b/chromium/net/android/network_change_notifier_delegate_android.h @@ -0,0 +1,82 @@ +// 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. + +#ifndef NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_ANDROID_H_ +#define NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_ANDROID_H_ + +#include "base/android/jni_android.h" +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list_threadsafe.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" +#include "net/base/network_change_notifier.h" + +namespace net { + +// Delegate used to thread-safely notify NetworkChangeNotifierAndroid whenever a +// network connection change notification is signaled by the Java side (on the +// JNI thread). +// All the methods exposed below must be called exclusively on the JNI thread +// unless otherwise stated (e.g. AddObserver()/RemoveObserver()). +class NET_EXPORT_PRIVATE NetworkChangeNotifierDelegateAndroid { + public: + typedef NetworkChangeNotifier::ConnectionType ConnectionType; + + // Observer interface implemented by NetworkChangeNotifierAndroid which + // subscribes to network change notifications fired by the delegate (and + // initiated by the Java side). + class Observer { + public: + virtual ~Observer() {} + + // Updates the current connection type. + virtual void OnConnectionTypeChanged() = 0; + }; + + NetworkChangeNotifierDelegateAndroid(); + ~NetworkChangeNotifierDelegateAndroid(); + + // Called from NetworkChangeNotifierAndroid.java on the JNI thread whenever + // the connection type changes. This updates the current connection type seen + // by this class and forwards the notification to the observers that + // subscribed through AddObserver(). + void NotifyConnectionTypeChanged(JNIEnv* env, + jobject obj, + jint new_connection_type); + jint GetConnectionType(JNIEnv* env, jobject obj) const; + + // These methods can be called on any thread. Note that the provided observer + // will be notified on the thread AddObserver() is called on. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Can be called from any thread. + ConnectionType GetCurrentConnectionType() const; + + // Initializes JNI bindings. + static bool Register(JNIEnv* env); + + private: + friend class BaseNetworkChangeNotifierAndroidTest; + + void SetCurrentConnectionType(ConnectionType connection_type); + + // Methods calling the Java side exposed for testing. + void SetOnline(); + void SetOffline(); + + base::ThreadChecker thread_checker_; + scoped_refptr<ObserverListThreadSafe<Observer> > observers_; + scoped_refptr<base::SingleThreadTaskRunner> jni_task_runner_; + base::android::ScopedJavaGlobalRef<jobject> java_network_change_notifier_; + mutable base::Lock connection_type_lock_; // Protects the state below. + ConnectionType connection_type_; + + DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierDelegateAndroid); +}; + +} // namespace net + +#endif // NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_H_ diff --git a/chromium/net/android/network_change_notifier_factory_android.cc b/chromium/net/android/network_change_notifier_factory_android.cc new file mode 100644 index 00000000000..64b9ca2a215 --- /dev/null +++ b/chromium/net/android/network_change_notifier_factory_android.cc @@ -0,0 +1,20 @@ +// 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. + +#include "net/android/network_change_notifier_factory_android.h" + +#include "net/android/network_change_notifier_android.h" +#include "net/android/network_change_notifier_delegate_android.h" + +namespace net { + +NetworkChangeNotifierFactoryAndroid::NetworkChangeNotifierFactoryAndroid() {} + +NetworkChangeNotifierFactoryAndroid::~NetworkChangeNotifierFactoryAndroid() {} + +NetworkChangeNotifier* NetworkChangeNotifierFactoryAndroid::CreateInstance() { + return new NetworkChangeNotifierAndroid(&delegate_); +} + +} // namespace net diff --git a/chromium/net/android/network_change_notifier_factory_android.h b/chromium/net/android/network_change_notifier_factory_android.h new file mode 100644 index 00000000000..e71ec27dca5 --- /dev/null +++ b/chromium/net/android/network_change_notifier_factory_android.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef NET_ANDROID_NETWORK_CHANGE_NOTIFIER_FACTORY_ANDROID_H_ +#define NET_ANDROID_NETWORK_CHANGE_NOTIFIER_FACTORY_ANDROID_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "net/android/network_change_notifier_delegate_android.h" +#include "net/base/net_export.h" +#include "net/base/network_change_notifier_factory.h" + +namespace net { + +class NetworkChangeNotifier; +class NetworkChangeNotifierDelegateAndroid; + +// NetworkChangeNotifierFactory creates Android-specific specialization of +// NetworkChangeNotifier. See network_change_notifier_android.h for more +// details. +class NET_EXPORT NetworkChangeNotifierFactoryAndroid : + public NetworkChangeNotifierFactory { + public: + // Must be called on the JNI thread. + NetworkChangeNotifierFactoryAndroid(); + + // Must be called on the JNI thread. + virtual ~NetworkChangeNotifierFactoryAndroid(); + + // NetworkChangeNotifierFactory: + virtual NetworkChangeNotifier* CreateInstance() OVERRIDE; + + private: + // Delegate passed to the instances created by this class. + NetworkChangeNotifierDelegateAndroid delegate_; + + DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierFactoryAndroid); +}; + +} // namespace net + +#endif // NET_ANDROID_NETWORK_CHANGE_NOTIFIER_FACTORY_H_ diff --git a/chromium/net/android/network_library.cc b/chromium/net/android/network_library.cc new file mode 100644 index 00000000000..2407100cdc3 --- /dev/null +++ b/chromium/net/android/network_library.cc @@ -0,0 +1,122 @@ +// 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. + +#include "net/android/network_library.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "jni/AndroidNetworkLibrary_jni.h" + +using base::android::AttachCurrentThread; +using base::android::ClearException; +using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; +using base::android::GetApplicationContext; +using base::android::ScopedJavaLocalRef; +using base::android::ToJavaArrayOfByteArray; +using base::android::ToJavaByteArray; + +namespace net { +namespace android { + +CertVerifyResultAndroid VerifyX509CertChain( + const std::vector<std::string>& cert_chain, + const std::string& auth_type) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jobjectArray> chain_byte_array = + ToJavaArrayOfByteArray(env, cert_chain); + DCHECK(!chain_byte_array.is_null()); + + ScopedJavaLocalRef<jstring> auth_string = + ConvertUTF8ToJavaString(env, auth_type); + DCHECK(!auth_string.is_null()); + + jint result = Java_AndroidNetworkLibrary_verifyServerCertificates( + env, chain_byte_array.obj(), auth_string.obj()); + + return static_cast<CertVerifyResultAndroid>(result); +} + +void AddTestRootCertificate(const uint8* cert, size_t len) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jbyteArray> cert_array = ToJavaByteArray(env, cert, len); + DCHECK(!cert_array.is_null()); + Java_AndroidNetworkLibrary_addTestRootCertificate(env, cert_array.obj()); +} + +void ClearTestRootCertificates() { + JNIEnv* env = AttachCurrentThread(); + Java_AndroidNetworkLibrary_clearTestRootCertificates(env); +} + +bool StoreKeyPair(const uint8* public_key, + size_t public_len, + const uint8* private_key, + size_t private_len) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jbyteArray> public_array = + ToJavaByteArray(env, public_key, public_len); + ScopedJavaLocalRef<jbyteArray> private_array = + ToJavaByteArray(env, private_key, private_len); + jboolean ret = Java_AndroidNetworkLibrary_storeKeyPair(env, + GetApplicationContext(), public_array.obj(), private_array.obj()); + LOG_IF(WARNING, !ret) << + "Call to Java_AndroidNetworkLibrary_storeKeyPair failed"; + return ret; +} + +void StoreCertificate(net::CertificateMimeType cert_type, + const void* data, + size_t data_len) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jbyteArray> data_array = + ToJavaByteArray(env, reinterpret_cast<const uint8*>(data), data_len); + jboolean ret = Java_AndroidNetworkLibrary_storeCertificate(env, + GetApplicationContext(), cert_type, data_array.obj()); + LOG_IF(WARNING, !ret) << + "Call to Java_AndroidNetworkLibrary_storeCertificate" + " failed"; + // Intentionally do not return 'ret', there is little the caller can + // do in case of failure (the CertInstaller itself will deal with + // incorrect data and display the appropriate toast). +} + +bool HaveOnlyLoopbackAddresses() { + JNIEnv* env = AttachCurrentThread(); + return Java_AndroidNetworkLibrary_haveOnlyLoopbackAddresses(env); +} + +std::string GetNetworkList() { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> ret = + Java_AndroidNetworkLibrary_getNetworkList(env); + return ConvertJavaStringToUTF8(ret); +} + +bool GetMimeTypeFromExtension(const std::string& extension, + std::string* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jstring> extension_string = + ConvertUTF8ToJavaString(env, extension); + ScopedJavaLocalRef<jstring> ret = + Java_AndroidNetworkLibrary_getMimeTypeFromExtension( + env, extension_string.obj()); + + if (!ret.obj()) + return false; + *result = ConvertJavaStringToUTF8(ret); + return true; +} + +bool RegisterNetworkLibrary(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace net diff --git a/chromium/net/android/network_library.h b/chromium/net/android/network_library.h new file mode 100644 index 00000000000..6bdc1ae427c --- /dev/null +++ b/chromium/net/android/network_library.h @@ -0,0 +1,77 @@ +// 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. + +#ifndef NET_ANDROID_NETWORK_LIBRARY_H_ +#define NET_ANDROID_NETWORK_LIBRARY_H_ + +#include <jni.h> + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "net/android/cert_verify_result_android.h" +#include "net/base/mime_util.h" +#include "net/base/net_export.h" + +namespace net { +namespace android { + +// |cert_chain| is DER encoded chain of certificates, with the server's own +// certificate listed first. +// |auth_type| is as per the Java X509Certificate.checkServerTrusted method. +CertVerifyResultAndroid VerifyX509CertChain( + const std::vector<std::string>& cert_chain, + const std::string& auth_type); + +// Adds a certificate as a root trust certificate to the trust manager. +// |cert| is DER encoded certificate, |len| is its length in bytes. +void AddTestRootCertificate(const uint8* cert, size_t len); + +// Removes all root certificates added by |AddTestRootCertificate| calls. +void ClearTestRootCertificates(); + +// Helper for the <keygen> handler. Passes the DER-encoded key pair via +// JNI to the Credentials store. Note that the public key must be a DER +// encoded SubjectPublicKeyInfo (X.509), as returned by i2d_PUBKEY() +// (and *not* i2d_PublicKey(), which returns a PKCS#1 key). +// +// Also, the private key must be in PKCS#8 format, as returned by +// i2d_PKCS8_PRIV_KEY_INFO(EVP_PKEY2PKCS8(pkey)), which is a different +// format than what i2d_PrivateKey() returns, so don't use it either. +// +bool StoreKeyPair(const uint8* public_key, + size_t public_len, + const uint8* private_key, + size_t private_len); + +// Helper used to pass the DER-encoded bytes of an X.509 certificate or +// a PKCS#12 archive holding a private key to the CertInstaller activity. +NET_EXPORT void StoreCertificate(net::CertificateMimeType cert_type, + const void* data, + size_t data_len); + +// Returns 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. +bool HaveOnlyLoopbackAddresses(); + +// Return a string containing a list of network interfaces, each item is a +// network name and address pair. +// e.g. "eth0,10.0.0.2;eth0,fe80::5054:ff:fe12:3456" is a result string +// containing two items. +std::string GetNetworkList(); + +// Get the mime type (if any) that is associated with the file extension. +// Returns true if a corresponding mime type exists. +bool GetMimeTypeFromExtension(const std::string& extension, + std::string* result); + +// Register JNI methods +NET_EXPORT bool RegisterNetworkLibrary(JNIEnv* env); + +} // namespace android +} // namespace net + +#endif // NET_ANDROID_NETWORK_LIBRARY_H_ diff --git a/chromium/net/android/private_key_type_list.h b/chromium/net/android/private_key_type_list.h new file mode 100644 index 00000000000..1eeea805fe9 --- /dev/null +++ b/chromium/net/android/private_key_type_list.h @@ -0,0 +1,12 @@ +// 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. + +#ifndef DEFINE_PRIVATE_KEY_TYPE +#error "Please define DEFINE_PRIVATE_KEY_TYPE before including this file." +#endif + +DEFINE_PRIVATE_KEY_TYPE(RSA, 0) +DEFINE_PRIVATE_KEY_TYPE(DSA, 1) +DEFINE_PRIVATE_KEY_TYPE(ECDSA, 2) +DEFINE_PRIVATE_KEY_TYPE(INVALID, 255) diff --git a/chromium/net/android/tools/proxy_test_cases.py b/chromium/net/android/tools/proxy_test_cases.py new file mode 100755 index 00000000000..3d6e8ec7479 --- /dev/null +++ b/chromium/net/android/tools/proxy_test_cases.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python +# 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. + +"""Generator script for proxy tests. + +See AndroidProxySelectorTest.java +and net/proxy/proxy_config_service_android_unittest.cc + +To generate C++, run this script without arguments. +To generate Java, run this script with -j argument. + +Note that this generator is not run as part of the build process because +we are assuming that these test cases will not change often. +""" + +import optparse + +test_cases = [ + { + "name": "NoProxy", + "description" : "Test direct mapping when no proxy defined.", + "properties" : { + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndPort", + "description" : "Test http.proxyHost and http.proxyPort works.", + "properties" : { + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostOnly", + "description" : "We should get the default port (80) for proxied hosts.", + "properties" : { + "http.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:80", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyPortOnly", + "description" : + "http.proxyPort only should not result in any hosts being proxied.", + "properties" : { + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT" + } + }, + { + "name": "HttpNonProxyHosts1", + "description" : "Test that HTTP non proxy hosts are mapped correctly", + "properties" : { + "http.nonProxyHosts" : "slashdot.org", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "http://slashdot.org/" : "DIRECT", + } + }, + { + "name": "HttpNonProxyHosts2", + "description" : "Test that | pattern works.", + "properties" : { + "http.nonProxyHosts" : "slashdot.org|freecode.net", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "http://slashdot.org/" : "DIRECT", + "http://freecode.net/" : "DIRECT", + } + }, + { + "name": "HttpNonProxyHosts3", + "description" : "Test that * pattern works.", + "properties" : { + "http.nonProxyHosts" : "*example.com", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "http://www.example.com/" : "DIRECT", + "http://slashdot.org/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "FtpNonProxyHosts", + "description" : "Test that FTP non proxy hosts are mapped correctly", + "properties" : { + "ftp.nonProxyHosts" : "slashdot.org", + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "FtpProxyHostAndPort", + "description" : "Test ftp.proxyHost and ftp.proxyPort works.", + "properties" : { + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "ftp://example.com/" : "PROXY httpproxy.com:8080", + "http://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "FtpProxyHostOnly", + "description" : "Test ftp.proxyHost and default port.", + "properties" : { + "ftp.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "ftp://example.com/" : "PROXY httpproxy.com:80", + "http://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpsProxyHostAndPort", + "description" : "Test https.proxyHost and https.proxyPort works.", + "properties" : { + "https.proxyHost" : "httpproxy.com", + "https.proxyPort" : "8080", + }, + "mappings" : { + "https://example.com/" : "PROXY httpproxy.com:8080", + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpsProxyHostOnly", + "description" : "Test https.proxyHost and default port.", + # Chromium differs from the Android platform by connecting to port 80 for + # HTTPS connections by default, hence cpp-only. + "cpp-only" : "", + "properties" : { + "https.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "https://example.com/" : "PROXY httpproxy.com:80", + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostIPv6", + "description" : "Test IPv6 https.proxyHost and default port.", + "cpp-only" : "", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + }, + "mappings" : { + "http://example.com/" : "PROXY [a:b:c::d:1]:80", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndPortIPv6", + "description" : "Test IPv6 http.proxyHost and http.proxyPort works.", + "cpp-only" : "", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY [a:b:c::d:1]:8080", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndInvalidPort", + "description" : "Test invalid http.proxyPort does not crash.", + "cpp-only" : "", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + "http.proxyPort" : "65536", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "DefaultProxyExplictPort", + "description" : + "Default http proxy is used if a scheme-specific one is not found.", + "properties" : { + "proxyHost" : "defaultproxy.com", + "proxyPort" : "8080", + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:8080", + "https://example.com/" : "PROXY defaultproxy.com:8080", + "ftp://example.com/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "DefaultProxyDefaultPort", + "description" : "Check that the default proxy port is as expected.", + # Chromium differs from the Android platform by connecting to port 80 for + # HTTPS connections by default, hence cpp-only. + "cpp-only" : "", + "properties" : { + "proxyHost" : "defaultproxy.com", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + "https://example.com/" : "PROXY defaultproxy.com:80", + } + }, + { + "name": "FallbackToSocks", + "description" : "SOCKS proxy is used if scheme-specific one is not found.", + "properties" : { + "http.proxyHost" : "defaultproxy.com", + "socksProxyHost" : "socksproxy.com" + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + "https://example.com/" : "SOCKS5 socksproxy.com:1080", + "ftp://example.com" : "SOCKS5 socksproxy.com:1080", + } + }, + { + "name": "SocksExplicitPort", + "description" : "SOCKS proxy port is used if specified", + "properties" : { + "socksProxyHost" : "socksproxy.com", + "socksProxyPort" : "9000", + }, + "mappings" : { + "http://example.com/" : "SOCKS5 socksproxy.com:9000", + } + }, + { + "name": "HttpProxySupercedesSocks", + "description" : "SOCKS proxy is ignored if default HTTP proxy defined.", + "properties" : { + "proxyHost" : "defaultproxy.com", + "socksProxyHost" : "socksproxy.com", + "socksProxyPort" : "9000", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + } + }, +] + +class GenerateCPlusPlus: + """Generate C++ test cases""" + + def Generate(self): + for test_case in test_cases: + print ("TEST_F(ProxyConfigServiceAndroidTest, %s) {" % test_case["name"]) + if "description" in test_case: + self._GenerateDescription(test_case["description"]); + self._GenerateConfiguration(test_case["properties"]) + self._GenerateMappings(test_case["mappings"]) + print "}" + print "" + + def _GenerateDescription(self, description): + print " // %s" % description + + def _GenerateConfiguration(self, properties): + for key in sorted(properties.iterkeys()): + print " AddProperty(\"%s\", \"%s\");" % (key, properties[key]) + print " ProxySettingsChanged();" + + def _GenerateMappings(self, mappings): + for url in sorted(mappings.iterkeys()): + print " TestMapping(\"%s\", \"%s\");" % (url, mappings[url]) + + +class GenerateJava: + """Generate Java test cases""" + + def Generate(self): + for test_case in test_cases: + if test_case.has_key("cpp-only"): + continue + if "description" in test_case: + self._GenerateDescription(test_case["description"]); + print " @SmallTest" + print " @Feature({\"AndroidWebView\"})" + print " public void test%s() throws Exception {" % test_case["name"] + self._GenerateConfiguration(test_case["properties"]) + self._GenerateMappings(test_case["mappings"]) + print " }" + print "" + + def _GenerateDescription(self, description): + print " /**" + print " * %s" % description + print " *" + print " * @throws Exception" + print " */" + + def _GenerateConfiguration(self, properties): + for key in sorted(properties.iterkeys()): + print " System.setProperty(\"%s\", \"%s\");" % ( + key, properties[key]) + + def _GenerateMappings(self, mappings): + for url in sorted(mappings.iterkeys()): + mapping = mappings[url] + if 'HTTPS' in mapping: + mapping = mapping.replace('HTTPS', 'PROXY') + print " checkMapping(\"%s\", \"%s\");" % (url, mapping) + + +def main(): + parser = optparse.OptionParser() + parser.add_option("-j", "--java", + action="store_true", dest="java"); + (options, args) = parser.parse_args(); + if options.java: + generator = GenerateJava() + else: + generator = GenerateCPlusPlus() + generator.Generate() + +if __name__ == '__main__': + main() |