mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2026-06-02 04:16:31 +03:00
Import chromium-64.0.3282.140
This commit is contained in:
10
net/android/java/NetError.template
Normal file
10
net/android/java/NetError.template
Normal file
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2016 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.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.SignalStrength;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import org.chromium.base.ApplicationState;
|
||||
import org.chromium.base.ApplicationStatus;
|
||||
import org.chromium.base.ContextUtils;
|
||||
import org.chromium.base.ThreadUtils;
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
|
||||
/**
|
||||
* This class provides the cellular signal strength using the APIs provided by Android. This class
|
||||
* is thread safe.
|
||||
*/
|
||||
@JNINamespace("net::android")
|
||||
public class AndroidCellularSignalStrength {
|
||||
// {@link mSignalLevel} is set to volatile since may be accessed across threads.
|
||||
private volatile int mSignalLevel = CellularSignalStrengthError.ERROR_NOT_SUPPORTED;
|
||||
|
||||
private static final AndroidCellularSignalStrength sInstance =
|
||||
new AndroidCellularSignalStrength();
|
||||
|
||||
/**
|
||||
* This class listens to the changes in the cellular signal strength level and updates {@link
|
||||
* mSignalLevel}. {@link CellStateListener} registers as a signal strength observer only if the
|
||||
* application has running activities.
|
||||
*/
|
||||
private class CellStateListener
|
||||
extends PhoneStateListener implements ApplicationStatus.ApplicationStateListener {
|
||||
private final TelephonyManager mTelephonyManager;
|
||||
|
||||
CellStateListener() {
|
||||
ThreadUtils.assertOnBackgroundThread();
|
||||
|
||||
mTelephonyManager =
|
||||
(TelephonyManager) ContextUtils.getApplicationContext().getSystemService(
|
||||
Context.TELEPHONY_SERVICE);
|
||||
|
||||
if (mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY) return;
|
||||
|
||||
ApplicationStatus.registerApplicationStateListener(this);
|
||||
onApplicationStateChange(ApplicationStatus.getStateForApplication());
|
||||
}
|
||||
|
||||
private void register() {
|
||||
mTelephonyManager.listen(this, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
|
||||
}
|
||||
|
||||
private void unregister() {
|
||||
mSignalLevel = CellularSignalStrengthError.ERROR_NOT_SUPPORTED;
|
||||
mTelephonyManager.listen(this, PhoneStateListener.LISTEN_NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
|
||||
if (ApplicationStatus.getStateForApplication()
|
||||
!= ApplicationState.HAS_RUNNING_ACTIVITIES) {
|
||||
return;
|
||||
}
|
||||
mSignalLevel = signalStrength.getLevel();
|
||||
}
|
||||
|
||||
// ApplicationStatus.ApplicationStateListener
|
||||
@Override
|
||||
public void onApplicationStateChange(int newState) {
|
||||
if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES) {
|
||||
register();
|
||||
} else if (newState == ApplicationState.HAS_PAUSED_ACTIVITIES) {
|
||||
unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AndroidCellularSignalStrength() {
|
||||
// {@link android.telephony.SignalStrength#getLevel} is only available on API Level
|
||||
// {@link Build.VERSION_CODES#M} and higher.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread("AndroidCellularSignalStrength");
|
||||
handlerThread.start();
|
||||
|
||||
new Handler(handlerThread.getLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new CellStateListener();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the signal strength level (between 0 and 4, both inclusive) for the currently
|
||||
* registered cellular network with lower value indicating lower signal strength. Returns
|
||||
* {@link CellularSignalStrengthError#ERROR_NOT_SUPPORTED} if the signal strength level is
|
||||
* unavailable.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@CalledByNative
|
||||
private static int getSignalStrengthLevel() {
|
||||
return sInstance.mSignalLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2014 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.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The result of a certification verification.
|
||||
*/
|
||||
@JNINamespace("net::android")
|
||||
public class AndroidCertVerifyResult {
|
||||
|
||||
/**
|
||||
* The verification status. One of the values in CertVerifyStatusAndroid.
|
||||
*/
|
||||
private final int mStatus;
|
||||
|
||||
/**
|
||||
* True if the root CA in the chain is in the system store.
|
||||
*/
|
||||
private final boolean mIsIssuedByKnownRoot;
|
||||
|
||||
/**
|
||||
* The properly ordered certificate chain used for verification.
|
||||
*/
|
||||
private final List<X509Certificate> mCertificateChain;
|
||||
|
||||
public AndroidCertVerifyResult(int status,
|
||||
boolean isIssuedByKnownRoot,
|
||||
List<X509Certificate> certificateChain) {
|
||||
mStatus = status;
|
||||
mIsIssuedByKnownRoot = isIssuedByKnownRoot;
|
||||
mCertificateChain = new ArrayList<X509Certificate>(certificateChain);
|
||||
}
|
||||
|
||||
public AndroidCertVerifyResult(int status) {
|
||||
mStatus = status;
|
||||
mIsIssuedByKnownRoot = false;
|
||||
mCertificateChain = Collections.<X509Certificate>emptyList();
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public int getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public boolean isIssuedByKnownRoot() {
|
||||
return mIsIssuedByKnownRoot;
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public byte[][] getCertificateChainEncoded() {
|
||||
byte[][] verifiedChainArray = new byte[mCertificateChain.size()][];
|
||||
try {
|
||||
for (int i = 0; i < mCertificateChain.size(); i++) {
|
||||
verifiedChainArray[i] = mCertificateChain.get(i).getEncoded();
|
||||
}
|
||||
} catch (CertificateEncodingException e) {
|
||||
return new byte[0][];
|
||||
}
|
||||
return verifiedChainArray;
|
||||
}
|
||||
}
|
||||
251
net/android/java/src/org/chromium/net/AndroidKeyStore.java
Normal file
251
net/android/java/src/org/chromium/net/AndroidKeyStore.java
Normal file
@@ -0,0 +1,251 @@
|
||||
// Copyright 2014 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.Log;
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
|
||||
/**
|
||||
* Specifies all the dependencies from the native OpenSSL engine on an Android KeyStore.
|
||||
*/
|
||||
@JNINamespace("net::android")
|
||||
public class AndroidKeyStore {
|
||||
private static final String TAG = "AndroidKeyStore";
|
||||
|
||||
/**
|
||||
* Sign a given message with a given PrivateKey object.
|
||||
*
|
||||
* @param privateKey The PrivateKey handle.
|
||||
* @param algorithm The signature algorithm to use.
|
||||
* @param message The message to sign.
|
||||
* @return signature as a byte buffer.
|
||||
*
|
||||
* Note: NONEwithRSA is not implemented in Android < 4.2. See
|
||||
* getOpenSSLHandleForPrivateKey() below for a work-around.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static byte[] signWithPrivateKey(
|
||||
PrivateKey privateKey, String algorithm, byte[] message) {
|
||||
// Hint: Algorithm names come from:
|
||||
// http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
|
||||
Signature signature = null;
|
||||
try {
|
||||
signature = Signature.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "Signature algorithm " + algorithm + " not supported: " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
signature.initSign(privateKey);
|
||||
signature.update(message);
|
||||
return signature.sign();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG,
|
||||
"Exception while signing message with " + algorithm + " and "
|
||||
+ privateKey.getAlgorithm() + " private key ("
|
||||
+ privateKey.getClass().getName() + "): " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getOpenSSLKeyForPrivateKey(PrivateKey privateKey) {
|
||||
// Sanity checks
|
||||
if (privateKey == null) {
|
||||
Log.e(TAG, "privateKey == null");
|
||||
return null;
|
||||
}
|
||||
if (!(privateKey instanceof RSAPrivateKey)) {
|
||||
Log.e(TAG, "does not implement RSAPrivateKey");
|
||||
return null;
|
||||
}
|
||||
// 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 null;
|
||||
}
|
||||
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 null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use reflection to invoke the 'getOpenSSLKey()' method on the
|
||||
// private key. This returns another Java object that wraps a native
|
||||
// EVP_PKEY and OpenSSLEngine. Note that the method is final in Android
|
||||
// 4.1, 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 null;
|
||||
}
|
||||
return opensslKey;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system EVP_PKEY handle corresponding to a given PrivateKey
|
||||
* object.
|
||||
*
|
||||
* This shall only be used when the "NONEwithRSA" signature is not
|
||||
* available, as described in signWithPrivateKey(). 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. It may not be used
|
||||
* with the Chromium version of OpenSSL (BoringSSL). See AndroidEVP_PKEY in
|
||||
* net/android/legacy_openssl.h.
|
||||
*
|
||||
* 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
|
||||
private static long getOpenSSLHandleForPrivateKey(PrivateKey privateKey) {
|
||||
Object opensslKey = getOpenSSLKeyForPrivateKey(privateKey);
|
||||
if (opensslKey == null) return 0;
|
||||
|
||||
try {
|
||||
// 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. Note that this
|
||||
// method these days returns a 64-bit long, but since this code
|
||||
// path is used for older Android versions, it may still return
|
||||
// a 32-bit int here. To be on the safe side, we cast the return
|
||||
// value via Number rather than directly to Integer or Long.
|
||||
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);
|
||||
long evp_pkey = 0;
|
||||
try {
|
||||
evp_pkey = ((Number) getPkeyContext.invoke(opensslKey)).longValue();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OpenSSLEngine object corresponding to a given PrivateKey
|
||||
* object.
|
||||
*
|
||||
* This shall only be used for Android 4.1 to work around a platform bug.
|
||||
* See https://crbug.com/381465.
|
||||
*
|
||||
* @param privateKey The PrivateKey handle.
|
||||
* @return The OpenSSLEngine object (or null if not available)
|
||||
*/
|
||||
@CalledByNative
|
||||
private static Object getOpenSSLEngineForPrivateKey(PrivateKey privateKey) {
|
||||
// Find the system OpenSSLEngine class.
|
||||
Class<?> engineClass;
|
||||
try {
|
||||
engineClass = Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLEngine");
|
||||
} 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 OpenSSLEngine class: " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
Object opensslKey = getOpenSSLKeyForPrivateKey(privateKey);
|
||||
if (opensslKey == null) return null;
|
||||
|
||||
try {
|
||||
// Use reflection to invoke the 'getEngine' method on the
|
||||
// result of the getOpenSSLKey().
|
||||
Method getEngine;
|
||||
try {
|
||||
getEngine = opensslKey.getClass().getDeclaredMethod("getEngine");
|
||||
} catch (Exception e) {
|
||||
// Bail here too, something really not working as expected.
|
||||
Log.e(TAG, "No getEngine() method on OpenSSLKey member:" + e);
|
||||
return null;
|
||||
}
|
||||
getEngine.setAccessible(true);
|
||||
Object engine = null;
|
||||
try {
|
||||
engine = getEngine.invoke(opensslKey);
|
||||
} finally {
|
||||
getEngine.setAccessible(false);
|
||||
}
|
||||
if (engine == null) {
|
||||
// The PrivateKey is probably rotten for some reason.
|
||||
Log.e(TAG, "getEngine() returned null");
|
||||
}
|
||||
// Sanity-check the returned engine.
|
||||
if (!engineClass.isInstance(engine)) {
|
||||
Log.e(TAG, "Engine is not an OpenSSLEngine instance, its class name is:"
|
||||
+ engine.getClass().getCanonicalName());
|
||||
return null;
|
||||
}
|
||||
return engine;
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception while trying to retrieve OpenSSLEngine object: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
466
net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
Normal file
466
net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
Normal file
@@ -0,0 +1,466 @@
|
||||
// Copyright 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.annotation.TargetApi;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.security.KeyChain;
|
||||
import android.security.NetworkSecurityPolicy;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.chromium.base.ContextUtils;
|
||||
import org.chromium.base.VisibleForTesting;
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.CalledByNativeUnchecked;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketImpl;
|
||||
import java.net.URLConnection;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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 publicKey The public key bytes as DER-encoded SubjectPublicKeyInfo (X.509)
|
||||
* @param privateKey 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
|
||||
public static boolean storeKeyPair(byte[] publicKey, byte[] privateKey) {
|
||||
// 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", privateKey);
|
||||
intent.putExtra("KEY", publicKey);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
ContextUtils.getApplicationContext().startActivity(intent);
|
||||
return true;
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w(TAG, "could not store key pair: " + 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
|
||||
public static 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
|
||||
public static 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the server's certificate chain is trusted. Note that the caller
|
||||
* must still verify the name matches that of the leaf certificate.
|
||||
*
|
||||
* @param certChain The ASN.1 DER encoded bytes for certificates.
|
||||
* @param authType The key exchange algorithm name (e.g. RSA).
|
||||
* @param host The hostname of the server.
|
||||
* @return Android certificate verification result code.
|
||||
*/
|
||||
@CalledByNative
|
||||
public static AndroidCertVerifyResult verifyServerCertificates(byte[][] certChain,
|
||||
String authType,
|
||||
String host) {
|
||||
try {
|
||||
return X509Util.verifyServerCertificates(certChain, authType, host);
|
||||
} catch (KeyStoreException e) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.FAILED);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.FAILED);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ISO country code equivalent of the current MCC.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static String getNetworkCountryIso() {
|
||||
TelephonyManager telephonyManager =
|
||||
(TelephonyManager) ContextUtils.getApplicationContext().getSystemService(
|
||||
Context.TELEPHONY_SERVICE);
|
||||
if (telephonyManager == null) return "";
|
||||
return telephonyManager.getNetworkCountryIso();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MCC+MNC (mobile country code + mobile network code) as
|
||||
* the numeric name of the current registered operator.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static String getNetworkOperator() {
|
||||
TelephonyManager telephonyManager =
|
||||
(TelephonyManager) ContextUtils.getApplicationContext().getSystemService(
|
||||
Context.TELEPHONY_SERVICE);
|
||||
if (telephonyManager == null) return "";
|
||||
return telephonyManager.getNetworkOperator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MCC+MNC (mobile country code + mobile network code) as
|
||||
* the numeric name of the current SIM operator.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static String getSimOperator() {
|
||||
TelephonyManager telephonyManager =
|
||||
(TelephonyManager) ContextUtils.getApplicationContext().getSystemService(
|
||||
Context.TELEPHONY_SERVICE);
|
||||
if (telephonyManager == null) return "";
|
||||
return telephonyManager.getSimOperator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the device is roaming on the currently active network. When true, it
|
||||
* suggests that use of data may incur extra costs.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static boolean getIsRoaming() {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) ContextUtils.getApplicationContext().getSystemService(
|
||||
Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
if (networkInfo == null) return false; // No active network.
|
||||
return networkInfo.isRoaming();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the system's captive portal probe was blocked for the current default data
|
||||
* network. The method will return false if the captive portal probe was not blocked, the login
|
||||
* process to the captive portal has been successfully completed, or if the captive portal
|
||||
* status can't be determined. Requires ACCESS_NETWORK_STATE permission. Only available on
|
||||
* Android Marshmallow and later versions. Returns false on earlier versions.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@CalledByNative
|
||||
private static boolean getIsCaptivePortal() {
|
||||
// NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL is only available on Marshmallow and
|
||||
// later versions.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) ContextUtils.getApplicationContext().getSystemService(
|
||||
Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivityManager == null) return false;
|
||||
|
||||
Network network = connectivityManager.getActiveNetwork();
|
||||
if (network == null) return false;
|
||||
|
||||
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
|
||||
return capabilities != null
|
||||
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SSID of the currently associated WiFi access point if there is one. Otherwise,
|
||||
* returns empty string.
|
||||
*/
|
||||
@CalledByNative
|
||||
public static String getWifiSSID() {
|
||||
final Intent intent = ContextUtils.getApplicationContext().registerReceiver(
|
||||
null, new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION));
|
||||
if (intent != null) {
|
||||
final WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
|
||||
if (wifiInfo != null) {
|
||||
final String ssid = wifiInfo.getSSID();
|
||||
if (ssid != null) {
|
||||
return ssid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static class NetworkSecurityPolicyProxy {
|
||||
private static NetworkSecurityPolicyProxy sInstance = new NetworkSecurityPolicyProxy();
|
||||
|
||||
public static NetworkSecurityPolicyProxy getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setInstanceForTesting(
|
||||
NetworkSecurityPolicyProxy networkSecurityPolicyProxy) {
|
||||
sInstance = networkSecurityPolicyProxy;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public boolean isCleartextTrafficPermitted(String host) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
// No per-host configuration before N.
|
||||
return isCleartextTrafficPermitted();
|
||||
}
|
||||
return NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(host);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public boolean isCleartextTrafficPermitted() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
// Always true before M.
|
||||
return true;
|
||||
}
|
||||
return NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if cleartext traffic to |host| is allowed by the current app.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static boolean isCleartextPermitted(String host) {
|
||||
try {
|
||||
return NetworkSecurityPolicyProxy.getInstance().isCleartextTrafficPermitted(host);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return NetworkSecurityPolicyProxy.getInstance().isCleartextTrafficPermitted();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@CalledByNative
|
||||
private static byte[][] getDnsServers() {
|
||||
ConnectivityManager connectivityManager =
|
||||
(ConnectivityManager) ContextUtils.getApplicationContext().getSystemService(
|
||||
Context.CONNECTIVITY_SERVICE);
|
||||
if (connectivityManager == null) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
Network network = connectivityManager.getActiveNetwork();
|
||||
if (network == null) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
|
||||
if (linkProperties == null) {
|
||||
return new byte[0][0];
|
||||
}
|
||||
List<InetAddress> dnsServersList = linkProperties.getDnsServers();
|
||||
byte[][] dnsServers = new byte[dnsServersList.size()][];
|
||||
for (int i = 0; i < dnsServersList.size(); i++) {
|
||||
dnsServers[i] = dnsServersList.get(i).getAddress();
|
||||
}
|
||||
return dnsServers;
|
||||
}
|
||||
|
||||
/** Socket that exists only to provide a file descriptor when queried. */
|
||||
private static class SocketFd extends Socket {
|
||||
/** SocketImpl that exists only to provide a file descriptor when queried. */
|
||||
private static class SocketImplFd extends SocketImpl {
|
||||
private final ParcelFileDescriptor mPfd;
|
||||
|
||||
/**
|
||||
* Create a {@link SocketImpl} that sets {@code fd} as the underlying file descriptor.
|
||||
* Does not take ownership of {@code fd}, i.e. {@link #close()} is a no-op.
|
||||
*/
|
||||
SocketImplFd(int fd) {
|
||||
mPfd = ParcelFileDescriptor.adoptFd(fd);
|
||||
this.fd = mPfd.getFileDescriptor();
|
||||
}
|
||||
|
||||
protected void accept(SocketImpl s) {
|
||||
throw new RuntimeException("accept not implemented");
|
||||
}
|
||||
protected int available() {
|
||||
throw new RuntimeException("accept not implemented");
|
||||
}
|
||||
protected void bind(InetAddress host, int port) {
|
||||
throw new RuntimeException("accept not implemented");
|
||||
}
|
||||
protected void close() {
|
||||
// Detach from |fd| to avoid leak detection false positives without closing |fd|.
|
||||
mPfd.detachFd();
|
||||
}
|
||||
protected void connect(InetAddress address, int port) {
|
||||
throw new RuntimeException("connect not implemented");
|
||||
}
|
||||
protected void connect(SocketAddress address, int timeout) {
|
||||
throw new RuntimeException("connect not implemented");
|
||||
}
|
||||
protected void connect(String host, int port) {
|
||||
throw new RuntimeException("connect not implemented");
|
||||
}
|
||||
protected void create(boolean stream) {
|
||||
throw new RuntimeException("create not implemented");
|
||||
}
|
||||
protected InputStream getInputStream() {
|
||||
throw new RuntimeException("getInputStream not implemented");
|
||||
}
|
||||
protected OutputStream getOutputStream() {
|
||||
throw new RuntimeException("getOutputStream not implemented");
|
||||
}
|
||||
protected void listen(int backlog) {
|
||||
throw new RuntimeException("listen not implemented");
|
||||
}
|
||||
protected void sendUrgentData(int data) {
|
||||
throw new RuntimeException("sendUrgentData not implemented");
|
||||
}
|
||||
public Object getOption(int optID) {
|
||||
throw new RuntimeException("getOption not implemented");
|
||||
}
|
||||
public void setOption(int optID, Object value) {
|
||||
throw new RuntimeException("setOption not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Socket} that sets {@code fd} as the underlying file descriptor.
|
||||
* Does not take ownership of {@code fd}, i.e. {@link #close()} is a no-op.
|
||||
*/
|
||||
SocketFd(int fd) throws IOException {
|
||||
super(new SocketImplFd(fd));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to wrap TrafficStats.setThreadStatsUid(int uid) and TrafficStats.clearThreadStatsUid()
|
||||
* which are hidden and so must be accessed via reflection.
|
||||
*/
|
||||
private static class ThreadStatsUid {
|
||||
// Reference to TrafficStats.setThreadStatsUid(int uid).
|
||||
private static final Method sSetThreadStatsUid;
|
||||
// Reference to TrafficStats.clearThreadStatsUid().
|
||||
private static final Method sClearThreadStatsUid;
|
||||
|
||||
// Get reference to TrafficStats.setThreadStatsUid(int uid) and
|
||||
// TrafficStats.clearThreadStatsUid() via reflection.
|
||||
static {
|
||||
try {
|
||||
sSetThreadStatsUid =
|
||||
TrafficStats.class.getMethod("setThreadStatsUid", Integer.TYPE);
|
||||
sClearThreadStatsUid = TrafficStats.class.getMethod("clearThreadStatsUid");
|
||||
} catch (NoSuchMethodException | SecurityException e) {
|
||||
throw new RuntimeException("Unable to get TrafficStats methods", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Calls TrafficStats.setThreadStatsUid(uid) */
|
||||
public static void set(int uid) throws IllegalAccessException, InvocationTargetException {
|
||||
sSetThreadStatsUid.invoke(null, uid); // Pass null for "this" as it's a static method.
|
||||
}
|
||||
|
||||
/** Calls TrafficStats.clearThreadStatsUid() */
|
||||
public static void clear() throws IllegalAccessException, InvocationTargetException {
|
||||
sClearThreadStatsUid.invoke(null); // Pass null for "this" as it's a static method.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag socket referenced by {@code fd} with {@code tag} for UID {@code uid}.
|
||||
*
|
||||
* Assumes thread UID tag isn't set upon entry, and ensures thread UID tag isn't set upon exit.
|
||||
* Unfortunately there is no TrafficStatis.getThreadStatsUid().
|
||||
*/
|
||||
@CalledByNative
|
||||
private static void tagSocket(int fd, int uid, int tag)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
// Set thread tags.
|
||||
int oldTag = TrafficStats.getThreadStatsTag();
|
||||
if (tag != oldTag) {
|
||||
TrafficStats.setThreadStatsTag(tag);
|
||||
}
|
||||
if (uid != TrafficStatsUid.UNSET) {
|
||||
ThreadStatsUid.set(uid);
|
||||
}
|
||||
|
||||
// Apply thread tags to socket.
|
||||
SocketFd s = new SocketFd(fd);
|
||||
TrafficStats.tagSocket(s);
|
||||
s.close(); // No-op, just to avoid leak detection false positives.
|
||||
|
||||
// Restore prior thread tags.
|
||||
if (tag != oldTag) {
|
||||
TrafficStats.setThreadStatsTag(oldTag);
|
||||
}
|
||||
if (uid != TrafficStatsUid.UNSET) {
|
||||
ThreadStatsUid.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2015 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.net.TrafficStats;
|
||||
import android.os.Process;
|
||||
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
|
||||
/**
|
||||
* This class interacts with TrafficStats API provided by Android.
|
||||
*/
|
||||
@JNINamespace("net::android::traffic_stats")
|
||||
public class AndroidTrafficStats {
|
||||
private AndroidTrafficStats() {}
|
||||
|
||||
/**
|
||||
* @return Number of bytes transmitted since device boot. Counts packets across all network
|
||||
* interfaces, and always increases monotonically since device boot. Statistics are
|
||||
* measured at the network layer, so they include both TCP and UDP usage.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static long getTotalTxBytes() {
|
||||
long bytes = TrafficStats.getTotalTxBytes();
|
||||
return bytes != TrafficStats.UNSUPPORTED ? bytes : TrafficStatsError.ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Number of bytes received since device boot. Counts packets across all network
|
||||
* interfaces, and always increases monotonically since device boot. Statistics are
|
||||
* measured at the network layer, so they include both TCP and UDP usage.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static long getTotalRxBytes() {
|
||||
long bytes = TrafficStats.getTotalRxBytes();
|
||||
return bytes != TrafficStats.UNSUPPORTED ? bytes : TrafficStatsError.ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Number of bytes transmitted since device boot that were attributed to caller's UID.
|
||||
* Counts packets across all network interfaces, and always increases monotonically
|
||||
* since device boot. Statistics are measured at the network layer, so they include
|
||||
* both TCP and UDP usage.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static long getCurrentUidTxBytes() {
|
||||
long bytes = TrafficStats.getUidTxBytes(Process.myUid());
|
||||
return bytes != TrafficStats.UNSUPPORTED ? bytes : TrafficStatsError.ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Number of bytes received since device boot that were attributed to caller's UID.
|
||||
* Counts packets across all network interfaces, and always increases monotonically
|
||||
* since device boot. Statistics are measured at the network layer, so they include
|
||||
* both TCP and UDP usage.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static long getCurrentUidRxBytes() {
|
||||
long bytes = TrafficStats.getUidRxBytes(Process.myUid());
|
||||
return bytes != TrafficStats.UNSUPPORTED ? bytes : TrafficStatsError.ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
38
net/android/java/src/org/chromium/net/GURLUtils.java
Normal file
38
net/android/java/src/org/chromium/net/GURLUtils.java
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 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.annotations.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);
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
// Copyright 2015 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.Manifest;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
|
||||
import org.chromium.base.ApplicationStatus;
|
||||
import org.chromium.base.ContextUtils;
|
||||
import org.chromium.base.Log;
|
||||
import org.chromium.base.ThreadUtils;
|
||||
import org.chromium.base.VisibleForTesting;
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class to get Auth Tokens for HTTP Negotiate authentication (typically used for Kerberos) An
|
||||
* instance of this class is created for each separate negotiation.
|
||||
*
|
||||
* Please keep the documentation from the chromium.org page (https://goo.gl/46hmKx) in sync.
|
||||
* ================================================================================================
|
||||
* In addition to the error codes that can be forwarded from the authenticator app, the following
|
||||
* errors can be displayed when trying to authenticate a request:
|
||||
*
|
||||
* - ERR_UNEXPECTED: An unexpected error happened and the request has been terminated.
|
||||
*
|
||||
* - ERR_MISSING_AUTH_CREDENTIALS: The account information is not usable. It can be raised for
|
||||
* example if the user did not log in to the authenticator app and no eligible account is found,
|
||||
* if the account information can't be obtained because the current app does not have the
|
||||
* required permissions, or if there is more than one eligible account and we can't obtain a
|
||||
* selection from the user.
|
||||
*
|
||||
* - ERR_MISCONFIGURED_AUTH_ENVIRONMENT: The authentication can't be completed because of some
|
||||
* issues in the configuration of the app. Some permissions may be missing.
|
||||
*
|
||||
* Please search for the "cr_net_auth" tag in logcat to have more information about the cause of
|
||||
* these errors.
|
||||
* ================================================================================================
|
||||
*/
|
||||
@JNINamespace("net::android")
|
||||
public class HttpNegotiateAuthenticator {
|
||||
private static final String TAG = "net_auth";
|
||||
private Bundle mSpnegoContext;
|
||||
private final String mAccountType;
|
||||
|
||||
/**
|
||||
* Structure designed to hold the data related to a specific request across the various
|
||||
* callbacks needed to complete it.
|
||||
*/
|
||||
static class RequestData {
|
||||
/** Native object to post the result to. */
|
||||
public long nativeResultObject;
|
||||
|
||||
/** Reference to the account manager to use for the various requests. */
|
||||
public AccountManager accountManager;
|
||||
|
||||
/** Authenticator-specific options for the request, used for AccountManager#getAuthToken. */
|
||||
public Bundle options;
|
||||
|
||||
/** Desired token type, used for AccountManager#getAuthToken. */
|
||||
public String authTokenType;
|
||||
|
||||
/** Account to fetch an auth token for. */
|
||||
public Account account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects to receive a single account as result, and uses that account to request a token
|
||||
* from the {@link AccountManager} provided via the {@link RequestData}
|
||||
*/
|
||||
@VisibleForTesting
|
||||
class GetAccountsCallback implements AccountManagerCallback<Account[]> {
|
||||
private final RequestData mRequestData;
|
||||
public GetAccountsCallback(RequestData requestData) {
|
||||
mRequestData = requestData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Account[]> future) {
|
||||
Account[] accounts;
|
||||
try {
|
||||
accounts = future.getResult();
|
||||
} catch (OperationCanceledException | AuthenticatorException | IOException e) {
|
||||
Log.w(TAG, "ERR_UNEXPECTED: Error while attempting to retrieve accounts.", e);
|
||||
nativeSetResult(mRequestData.nativeResultObject, NetError.ERR_UNEXPECTED, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (accounts.length == 0) {
|
||||
Log.w(TAG, "ERR_MISSING_AUTH_CREDENTIALS: No account provided for the kerberos "
|
||||
+ "authentication. Please verify the configuration policies and "
|
||||
+ "that the CONTACTS runtime permission is granted. ");
|
||||
nativeSetResult(mRequestData.nativeResultObject,
|
||||
NetError.ERR_MISSING_AUTH_CREDENTIALS, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (accounts.length > 1) {
|
||||
Log.w(TAG, "ERR_MISSING_AUTH_CREDENTIALS: Found %d accounts eligible for the "
|
||||
+ "kerberos authentication. Please fix the configuration by "
|
||||
+ "providing a single account.",
|
||||
accounts.length);
|
||||
nativeSetResult(mRequestData.nativeResultObject,
|
||||
NetError.ERR_MISSING_AUTH_CREDENTIALS, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lacksPermission(ContextUtils.getApplicationContext(),
|
||||
"android.permission.USE_CREDENTIALS", true)) {
|
||||
// Protecting the AccountManager#getAuthToken call.
|
||||
// API < 23 Requires the USE_CREDENTIALS permission or throws an exception.
|
||||
// API >= 23 USE_CREDENTIALS permission is removed
|
||||
Log.e(TAG, "ERR_MISCONFIGURED_AUTH_ENVIRONMENT: USE_CREDENTIALS permission not "
|
||||
+ "granted. Aborting authentication.");
|
||||
nativeSetResult(mRequestData.nativeResultObject,
|
||||
NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
|
||||
return;
|
||||
}
|
||||
mRequestData.account = accounts[0];
|
||||
mRequestData.accountManager.getAuthToken(mRequestData.account,
|
||||
mRequestData.authTokenType, mRequestData.options, true /* notifyAuthFailure */,
|
||||
new GetTokenCallback(mRequestData),
|
||||
new Handler(ThreadUtils.getUiThreadLooper()));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
class GetTokenCallback implements AccountManagerCallback<Bundle> {
|
||||
private final RequestData mRequestData;
|
||||
public GetTokenCallback(RequestData requestData) {
|
||||
mRequestData = requestData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Bundle> future) {
|
||||
Bundle result;
|
||||
try {
|
||||
result = future.getResult();
|
||||
} catch (OperationCanceledException | AuthenticatorException | IOException e) {
|
||||
Log.w(TAG, "ERR_UNEXPECTED: Error while attempting to obtain a token.", e);
|
||||
nativeSetResult(mRequestData.nativeResultObject, NetError.ERR_UNEXPECTED, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.containsKey(AccountManager.KEY_INTENT)) {
|
||||
final Context appContext = ContextUtils.getApplicationContext();
|
||||
|
||||
// We wait for a broadcast that should be sent once the user is done interacting
|
||||
// with the notification
|
||||
// TODO(dgn) We currently hang around if the notification is swiped away, until
|
||||
// a LOGIN_ACCOUNTS_CHANGED_ACTION filter is received. It might be for something
|
||||
// unrelated then we would wait again here. Maybe we should limit the number of
|
||||
// retries in some way?
|
||||
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
appContext.unregisterReceiver(this);
|
||||
mRequestData.accountManager.getAuthToken(mRequestData.account,
|
||||
mRequestData.authTokenType, mRequestData.options,
|
||||
true /* notifyAuthFailure */, new GetTokenCallback(mRequestData),
|
||||
null);
|
||||
}
|
||||
|
||||
};
|
||||
appContext.registerReceiver(broadcastReceiver,
|
||||
new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION));
|
||||
} else {
|
||||
processResult(result, mRequestData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected HttpNegotiateAuthenticator(String accountType) {
|
||||
assert !android.text.TextUtils.isEmpty(accountType);
|
||||
mAccountType = accountType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param accountType The Android account type to use.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@CalledByNative
|
||||
static HttpNegotiateAuthenticator create(String accountType) {
|
||||
return new HttpNegotiateAuthenticator(accountType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nativeResultObject The C++ object used to return the result. For correct C++ memory
|
||||
* management we must call nativeSetResult precisely once with this object.
|
||||
* @param principal The principal (must be host based).
|
||||
* @param authToken The incoming auth token.
|
||||
* @param canDelegate True if we can delegate.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@CalledByNative
|
||||
void getNextAuthToken(final long nativeResultObject, final String principal, String authToken,
|
||||
boolean canDelegate) {
|
||||
assert principal != null;
|
||||
|
||||
Context applicationContext = ContextUtils.getApplicationContext();
|
||||
RequestData requestData = new RequestData();
|
||||
requestData.authTokenType = HttpNegotiateConstants.SPNEGO_TOKEN_TYPE_BASE + principal;
|
||||
requestData.accountManager = AccountManager.get(applicationContext);
|
||||
requestData.nativeResultObject = nativeResultObject;
|
||||
String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE};
|
||||
|
||||
requestData.options = new Bundle();
|
||||
if (authToken != null) {
|
||||
requestData.options.putString(
|
||||
HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN, authToken);
|
||||
}
|
||||
if (mSpnegoContext != null) {
|
||||
requestData.options.putBundle(
|
||||
HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, mSpnegoContext);
|
||||
}
|
||||
requestData.options.putBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE, canDelegate);
|
||||
|
||||
Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
|
||||
if (activity == null) {
|
||||
requestTokenWithoutActivity(applicationContext, requestData, features);
|
||||
} else {
|
||||
requestTokenWithActivity(applicationContext, activity, requestData, features);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a result bundle from a completed token request, communicating its content back to
|
||||
* the native code.
|
||||
*/
|
||||
private void processResult(Bundle result, RequestData requestData) {
|
||||
mSpnegoContext = result.getBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT);
|
||||
int status;
|
||||
switch (result.getInt(
|
||||
HttpNegotiateConstants.KEY_SPNEGO_RESULT, HttpNegotiateConstants.ERR_UNEXPECTED)) {
|
||||
case HttpNegotiateConstants.OK:
|
||||
status = 0;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_UNEXPECTED:
|
||||
status = NetError.ERR_UNEXPECTED;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_ABORTED:
|
||||
status = NetError.ERR_ABORTED;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS:
|
||||
status = NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_INVALID_RESPONSE:
|
||||
status = NetError.ERR_INVALID_RESPONSE;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS:
|
||||
status = NetError.ERR_INVALID_AUTH_CREDENTIALS;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME:
|
||||
status = NetError.ERR_UNSUPPORTED_AUTH_SCHEME;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS:
|
||||
status = NetError.ERR_MISSING_AUTH_CREDENTIALS;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS:
|
||||
status = NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
|
||||
break;
|
||||
case HttpNegotiateConstants.ERR_MALFORMED_IDENTITY:
|
||||
status = NetError.ERR_MALFORMED_IDENTITY;
|
||||
break;
|
||||
default:
|
||||
status = NetError.ERR_UNEXPECTED;
|
||||
}
|
||||
nativeSetResult(requestData.nativeResultObject, status,
|
||||
result.getString(AccountManager.KEY_AUTHTOKEN));
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an authentication token. If the account is not properly setup, it will result in
|
||||
* a failure.
|
||||
*
|
||||
* @param ctx The application context
|
||||
* @param requestData The object holding the data related to the current request
|
||||
* @param features An array of the account features to require, may be null or empty
|
||||
*/
|
||||
private void requestTokenWithoutActivity(
|
||||
Context ctx, RequestData requestData, String[] features) {
|
||||
if (lacksPermission(ctx, Manifest.permission.GET_ACCOUNTS, true /* onlyPreM */)) {
|
||||
// Protecting the AccountManager#getAccountsByTypeAndFeatures call.
|
||||
// API < 23 Requires the GET_ACCOUNTS permission or throws an exception.
|
||||
// API >= 23 Requires the GET_ACCOUNTS permission (CONTACTS permission group) or
|
||||
// returns only the accounts whose authenticator has a signature that
|
||||
// matches our app. Working with this restriction and not requesting
|
||||
// the permission is a valid use case in the context of WebView, so we
|
||||
// don't require it on M+
|
||||
Log.e(TAG, "ERR_MISCONFIGURED_AUTH_ENVIRONMENT: GET_ACCOUNTS permission not "
|
||||
+ "granted. Aborting authentication.");
|
||||
nativeSetResult(requestData.nativeResultObject,
|
||||
NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
|
||||
return;
|
||||
}
|
||||
requestData.accountManager.getAccountsByTypeAndFeatures(mAccountType, features,
|
||||
new GetAccountsCallback(requestData), new Handler(ThreadUtils.getUiThreadLooper()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an authentication token. Handles the account selection/creation and the credentials
|
||||
* confirmation if that is needed.
|
||||
* If there is more than one account, it will show an account picker dialog for
|
||||
* each query (e.g. html file, then favicon...)
|
||||
* Same if the credentials need to be confirmed.
|
||||
*
|
||||
* @param ctx The application context
|
||||
* @param activity The Activity context to use for launching new sub-Activities to prompt to
|
||||
* add an account, select an account, and/or enter a password, as necessary;
|
||||
* used only to call startActivity(); should not be null
|
||||
* @param requestData The object holding the data related to the current request
|
||||
* @param features An array of the account features to require, may be null or empty
|
||||
*/
|
||||
private void requestTokenWithActivity(
|
||||
Context ctx, Activity activity, RequestData requestData, String[] features) {
|
||||
boolean isPreM = Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
|
||||
String permission = isPreM
|
||||
? "android.permission.MANAGE_ACCOUNTS"
|
||||
: Manifest.permission.GET_ACCOUNTS;
|
||||
|
||||
// Check if the AccountManager#getAuthTokenByFeatures call can be made.
|
||||
// API < 23 Requires the MANAGE_ACCOUNTS permission.
|
||||
// API >= 23 Requires the GET_ACCOUNTS permission to behave properly. When it's not granted,
|
||||
// accounts not managed by the current application can't be retrieved. Depending
|
||||
// on the authenticator implementation, it might prompt to create an account, but
|
||||
// that won't be saved. This would be a bad user experience, so we also consider
|
||||
// it a failure case.
|
||||
if (lacksPermission(ctx, permission, isPreM)) {
|
||||
Log.e(TAG, "ERR_MISCONFIGURED_AUTH_ENVIRONMENT: %s permission not granted. "
|
||||
+ "Aborting authentication", permission);
|
||||
nativeSetResult(requestData.nativeResultObject,
|
||||
NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
|
||||
return;
|
||||
}
|
||||
|
||||
requestData.accountManager.getAuthTokenByFeatures(mAccountType, requestData.authTokenType,
|
||||
features, activity, null, requestData.options, new GetTokenCallback(requestData),
|
||||
new Handler(ThreadUtils.getUiThreadLooper()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current context lacks a given permission. Skips the check on M+ systems
|
||||
* if {@code onlyPreM} is {@code true}, and just returns {@code false}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean lacksPermission(Context context, String permission, boolean onlyPreM) {
|
||||
if (onlyPreM && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) return false;
|
||||
|
||||
int permissionResult =
|
||||
context.checkPermission(permission, Process.myPid(), Process.myUid());
|
||||
return permissionResult != PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
native void nativeSetResult(
|
||||
long nativeJavaNegotiateResultWrapper, int status, String authToken);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2015 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;
|
||||
|
||||
/**
|
||||
* Constants used by Chrome in SPNEGO authentication requests to the Android Account Manager.
|
||||
*/
|
||||
public class HttpNegotiateConstants {
|
||||
// Option bundle keys
|
||||
//
|
||||
// The token provided by in the HTTP 401 response (Base64 encoded string)
|
||||
public static final String KEY_INCOMING_AUTH_TOKEN = "incomingAuthToken";
|
||||
// The SPNEGO Context from the previous transaction (Bundle) - also used in the response bundle
|
||||
public static final String KEY_SPNEGO_CONTEXT = "spnegoContext";
|
||||
// True if delegation is allowed
|
||||
public static final String KEY_CAN_DELEGATE = "canDelegate";
|
||||
|
||||
// Response bundle keys
|
||||
//
|
||||
// The returned status from the authenticator.
|
||||
public static final String KEY_SPNEGO_RESULT = "spnegoResult";
|
||||
|
||||
// Name of SPNEGO feature
|
||||
public static final String SPNEGO_FEATURE = "SPNEGO";
|
||||
// Prefix of token type. Full token type is "SPNEGO:HOSTBASED:<spn>"
|
||||
public static final String SPNEGO_TOKEN_TYPE_BASE = "SPNEGO:HOSTBASED:";
|
||||
|
||||
// Returned status codes
|
||||
// All OK. Returned token is valid.
|
||||
public static final int OK = 0;
|
||||
// An unexpected error. This may be caused by a programming mistake or an invalid assumption.
|
||||
public static final int ERR_UNEXPECTED = 1;
|
||||
// Request aborted due to user action.
|
||||
public static final int ERR_ABORTED = 2;
|
||||
// An unexpected, but documented, SSPI or GSSAPI status code was returned.
|
||||
public static final int ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS = 3;
|
||||
// The server's response was invalid.
|
||||
public static final int ERR_INVALID_RESPONSE = 4;
|
||||
// Credentials could not be established during HTTP Authentication.
|
||||
public static final int ERR_INVALID_AUTH_CREDENTIALS = 5;
|
||||
// An HTTP Authentication scheme was tried which is not supported on this machine.
|
||||
public static final int ERR_UNSUPPORTED_AUTH_SCHEME = 6;
|
||||
// (GSSAPI) No Kerberos credentials were available during HTTP Authentication.
|
||||
public static final int ERR_MISSING_AUTH_CREDENTIALS = 7;
|
||||
// An undocumented SSPI or GSSAPI status code was returned.
|
||||
public static final int ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS = 8;
|
||||
// The identity used for authentication is invalid.
|
||||
public static final int ERR_MALFORMED_IDENTITY = 9;
|
||||
}
|
||||
104
net/android/java/src/org/chromium/net/NetStringUtil.java
Normal file
104
net/android/java/src/org/chromium/net/NetStringUtil.java
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2014 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.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.text.Normalizer;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility functions for converting strings between formats when not built with
|
||||
* icu.
|
||||
*/
|
||||
@JNINamespace("net::android")
|
||||
public class NetStringUtil {
|
||||
/**
|
||||
* Attempts to convert text in a given character set to a Unicode string.
|
||||
* Returns null on failure.
|
||||
* @param text ByteBuffer containing the character array to convert.
|
||||
* @param charsetName Character set it's in encoded in.
|
||||
* @return: Unicode string on success, null on failure.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static String convertToUnicode(
|
||||
ByteBuffer text,
|
||||
String charsetName) {
|
||||
try {
|
||||
Charset charset = Charset.forName(charsetName);
|
||||
CharsetDecoder decoder = charset.newDecoder();
|
||||
// On invalid characters, this will throw an exception.
|
||||
return decoder.decode(text).toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to convert text in a given character set to a Unicode string,
|
||||
* and normalize it. Returns null on failure.
|
||||
* @param text ByteBuffer containing the character array to convert.
|
||||
* @param charsetName Character set it's in encoded in.
|
||||
* @return: Unicode string on success, null on failure.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static String convertToUnicodeAndNormalize(
|
||||
ByteBuffer text,
|
||||
String charsetName) {
|
||||
String unicodeString = convertToUnicode(text, charsetName);
|
||||
if (unicodeString == null) return null;
|
||||
return Normalizer.normalize(unicodeString, Normalizer.Form.NFC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert text in a given character set to a Unicode string. Any invalid
|
||||
* characters are replaced with U+FFFD. Returns null if the character set
|
||||
* is not recognized.
|
||||
* @param text ByteBuffer containing the character array to convert.
|
||||
* @param charsetName Character set it's in encoded in.
|
||||
* @return: Unicode string on success, null on failure.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static String convertToUnicodeWithSubstitutions(
|
||||
ByteBuffer text,
|
||||
String charsetName) {
|
||||
try {
|
||||
Charset charset = Charset.forName(charsetName);
|
||||
|
||||
// TODO(mmenke): Investigate if Charset.decode() can be used
|
||||
// instead. The question is whether it uses the proper replace
|
||||
// character. JDK CharsetDecoder docs say U+FFFD is the default,
|
||||
// but Charset.decode() docs say it uses the "charset's default
|
||||
// replacement byte array".
|
||||
CharsetDecoder decoder = charset.newDecoder();
|
||||
decoder.onMalformedInput(CodingErrorAction.REPLACE);
|
||||
decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
||||
decoder.replaceWith("\uFFFD");
|
||||
return decoder.decode(text).toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to uppercase.
|
||||
* @param str String to convert.
|
||||
* @return: String converted to uppercase using default locale,
|
||||
* null on failure.
|
||||
*/
|
||||
@CalledByNative
|
||||
private static String toUpperCase(String str) {
|
||||
try {
|
||||
return str.toUpperCase(Locale.getDefault());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
444
net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
Normal file
444
net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
Normal file
@@ -0,0 +1,444 @@
|
||||
// Copyright 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.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Build;
|
||||
|
||||
import org.chromium.base.ContextUtils;
|
||||
import org.chromium.base.ObserverList;
|
||||
import org.chromium.base.VisibleForTesting;
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
import org.chromium.base.annotations.NativeClassQualifiedName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Triggers updates to the underlying network state in Chrome.
|
||||
*
|
||||
* By default, connectivity is assumed and changes must be 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);
|
||||
}
|
||||
|
||||
private final ArrayList<Long> mNativeChangeNotifiers;
|
||||
private final ObserverList<ConnectionTypeObserver> mConnectionTypeObservers;
|
||||
private final ConnectivityManager mConnectivityManager;
|
||||
private NetworkChangeNotifierAutoDetect mAutoDetector;
|
||||
// Last value broadcast via ConnectionTypeChange signal.
|
||||
private int mCurrentConnectionType = ConnectionType.CONNECTION_UNKNOWN;
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static NetworkChangeNotifier sInstance;
|
||||
|
||||
@VisibleForTesting
|
||||
protected NetworkChangeNotifier() {
|
||||
mNativeChangeNotifiers = new ArrayList<Long>();
|
||||
mConnectionTypeObservers = new ObserverList<ConnectionTypeObserver>();
|
||||
mConnectivityManager =
|
||||
(ConnectivityManager) ContextUtils.getApplicationContext().getSystemService(
|
||||
Context.CONNECTIVITY_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the singleton once.
|
||||
*/
|
||||
@CalledByNative
|
||||
public static NetworkChangeNotifier init() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new NetworkChangeNotifier();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public static boolean isInitialized() {
|
||||
return sInstance != null;
|
||||
}
|
||||
|
||||
static void resetInstanceForTests(NetworkChangeNotifier notifier) {
|
||||
sInstance = notifier;
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public int getCurrentConnectionType() {
|
||||
return mCurrentConnectionType;
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public int getCurrentConnectionSubtype() {
|
||||
return mAutoDetector == null
|
||||
? ConnectionSubtype.SUBTYPE_UNKNOWN
|
||||
: mAutoDetector.getCurrentNetworkState().getConnectionSubtype();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns NetID of device's current default connected network used for
|
||||
* communication. Only available on Lollipop and newer releases and when
|
||||
* auto-detection has been enabled, returns NetId.INVALID otherwise.
|
||||
*/
|
||||
@CalledByNative
|
||||
public long getCurrentDefaultNetId() {
|
||||
return mAutoDetector == null ? NetId.INVALID : mAutoDetector.getDefaultNetId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all of the device's currently connected
|
||||
* networks and ConnectionTypes. Array elements are a repeated sequence of:
|
||||
* NetID of network
|
||||
* ConnectionType of network
|
||||
* Only available on Lollipop and newer releases and when auto-detection has
|
||||
* been enabled.
|
||||
*/
|
||||
@CalledByNative
|
||||
public long[] getCurrentNetworksAndTypes() {
|
||||
return mAutoDetector == null ? new long[0] : mAutoDetector.getNetworksAndTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a native-side observer.
|
||||
*/
|
||||
@CalledByNative
|
||||
public void addNativeObserver(long nativeChangeNotifier) {
|
||||
mNativeChangeNotifiers.add(nativeChangeNotifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a native-side observer.
|
||||
*/
|
||||
@CalledByNative
|
||||
public void removeNativeObserver(long nativeChangeNotifier) {
|
||||
mNativeChangeNotifiers.remove(nativeChangeNotifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if NetworkCallback failed to register, indicating that network-specific
|
||||
* callbacks will not be issued.
|
||||
*/
|
||||
@CalledByNative
|
||||
public boolean registerNetworkCallbackFailed() {
|
||||
return mAutoDetector == null ? false : mAutoDetector.registerNetworkCallbackFailed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. Also note that in this case the auto detection is enabled based on the status of
|
||||
* the application (@see ApplicationStatus).
|
||||
*
|
||||
* @param shouldAutoDetect true if the NetworkChangeNotifier should listen for system changes in
|
||||
* network connectivity.
|
||||
*/
|
||||
public static void setAutoDetectConnectivityState(boolean shouldAutoDetect) {
|
||||
getInstance().setAutoDetectConnectivityStateInternal(
|
||||
shouldAutoDetect, new RegistrationPolicyApplicationStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers to always receive network change notifications no matter if
|
||||
* the app is in the background or foreground.
|
||||
* Note that in normal circumstances, chrome embedders should use
|
||||
* {@code setAutoDetectConnectivityState} to listen to network changes only
|
||||
* when the app is in the foreground, because network change observers
|
||||
* might perform expensive work depending on the network connectivity.
|
||||
*/
|
||||
public static void registerToReceiveNotificationsAlways() {
|
||||
getInstance().setAutoDetectConnectivityStateInternal(
|
||||
true, new RegistrationPolicyAlwaysRegister());
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers to receive network change notification based on the provided registration policy.
|
||||
*/
|
||||
public static void setAutoDetectConnectivityState(
|
||||
NetworkChangeNotifierAutoDetect.RegistrationPolicy policy) {
|
||||
getInstance().setAutoDetectConnectivityStateInternal(true, policy);
|
||||
}
|
||||
|
||||
private void destroyAutoDetector() {
|
||||
if (mAutoDetector != null) {
|
||||
mAutoDetector.destroy();
|
||||
mAutoDetector = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setAutoDetectConnectivityStateInternal(
|
||||
boolean shouldAutoDetect, NetworkChangeNotifierAutoDetect.RegistrationPolicy policy) {
|
||||
if (shouldAutoDetect) {
|
||||
if (mAutoDetector == null) {
|
||||
mAutoDetector = new NetworkChangeNotifierAutoDetect(
|
||||
new NetworkChangeNotifierAutoDetect.Observer() {
|
||||
@Override
|
||||
public void onConnectionTypeChanged(int newConnectionType) {
|
||||
updateCurrentConnectionType(newConnectionType);
|
||||
}
|
||||
@Override
|
||||
public void onConnectionSubtypeChanged(int newConnectionSubtype) {
|
||||
notifyObserversOfConnectionSubtypeChange(newConnectionSubtype);
|
||||
}
|
||||
@Override
|
||||
public void onNetworkConnect(long netId, int connectionType) {
|
||||
notifyObserversOfNetworkConnect(netId, connectionType);
|
||||
}
|
||||
@Override
|
||||
public void onNetworkSoonToDisconnect(long netId) {
|
||||
notifyObserversOfNetworkSoonToDisconnect(netId);
|
||||
}
|
||||
@Override
|
||||
public void onNetworkDisconnect(long netId) {
|
||||
notifyObserversOfNetworkDisconnect(netId);
|
||||
}
|
||||
@Override
|
||||
public void purgeActiveNetworkList(long[] activeNetIds) {
|
||||
notifyObserversToPurgeActiveNetworkList(activeNetIds);
|
||||
}
|
||||
},
|
||||
policy);
|
||||
final NetworkChangeNotifierAutoDetect.NetworkState networkState =
|
||||
mAutoDetector.getCurrentNetworkState();
|
||||
updateCurrentConnectionType(networkState.getConnectionType());
|
||||
notifyObserversOfConnectionSubtypeChange(networkState.getConnectionSubtype());
|
||||
}
|
||||
} else {
|
||||
destroyAutoDetector();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing, 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 != ConnectionType.CONNECTION_NONE;
|
||||
if (connectionCurrentlyExists != forceOnline) {
|
||||
updateCurrentConnectionType(forceOnline ? ConnectionType.CONNECTION_UNKNOWN
|
||||
: ConnectionType.CONNECTION_NONE);
|
||||
notifyObserversOfConnectionSubtypeChange(forceOnline ? ConnectionSubtype.SUBTYPE_UNKNOWN
|
||||
: ConnectionSubtype.SUBTYPE_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
// For testing, pretend a network connected.
|
||||
@CalledByNative
|
||||
public static void fakeNetworkConnected(long netId, int connectionType) {
|
||||
setAutoDetectConnectivityState(false);
|
||||
getInstance().notifyObserversOfNetworkConnect(netId, connectionType);
|
||||
}
|
||||
|
||||
// For testing, pretend a network will soon disconnect.
|
||||
@CalledByNative
|
||||
public static void fakeNetworkSoonToBeDisconnected(long netId) {
|
||||
setAutoDetectConnectivityState(false);
|
||||
getInstance().notifyObserversOfNetworkSoonToDisconnect(netId);
|
||||
}
|
||||
|
||||
// For testing, pretend a network disconnected.
|
||||
@CalledByNative
|
||||
public static void fakeNetworkDisconnected(long netId) {
|
||||
setAutoDetectConnectivityState(false);
|
||||
getInstance().notifyObserversOfNetworkDisconnect(netId);
|
||||
}
|
||||
|
||||
// For testing, pretend a network lists should be purged.
|
||||
@CalledByNative
|
||||
public static void fakePurgeActiveNetworkList(long[] activeNetIds) {
|
||||
setAutoDetectConnectivityState(false);
|
||||
getInstance().notifyObserversToPurgeActiveNetworkList(activeNetIds);
|
||||
}
|
||||
|
||||
// For testing, pretend a default network changed.
|
||||
@CalledByNative
|
||||
public static void fakeDefaultNetwork(long netId, int connectionType) {
|
||||
setAutoDetectConnectivityState(false);
|
||||
getInstance().notifyObserversOfConnectionTypeChange(connectionType, netId);
|
||||
}
|
||||
|
||||
// For testing, pretend the connection subtype has changed.
|
||||
@CalledByNative
|
||||
public static void fakeConnectionSubtypeChanged(int connectionSubtype) {
|
||||
setAutoDetectConnectivityState(false);
|
||||
getInstance().notifyObserversOfConnectionSubtypeChange(connectionSubtype);
|
||||
}
|
||||
|
||||
private void updateCurrentConnectionType(int newConnectionType) {
|
||||
mCurrentConnectionType = newConnectionType;
|
||||
notifyObserversOfConnectionTypeChange(newConnectionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts all observers of a connection change.
|
||||
*/
|
||||
void notifyObserversOfConnectionTypeChange(int newConnectionType) {
|
||||
notifyObserversOfConnectionTypeChange(newConnectionType, getCurrentDefaultNetId());
|
||||
}
|
||||
|
||||
private void notifyObserversOfConnectionTypeChange(int newConnectionType, long defaultNetId) {
|
||||
for (Long nativeChangeNotifier : mNativeChangeNotifiers) {
|
||||
nativeNotifyConnectionTypeChanged(
|
||||
nativeChangeNotifier, newConnectionType, defaultNetId);
|
||||
}
|
||||
for (ConnectionTypeObserver observer : mConnectionTypeObservers) {
|
||||
observer.onConnectionTypeChanged(newConnectionType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts all observers of a bandwidth change.
|
||||
*/
|
||||
void notifyObserversOfConnectionSubtypeChange(int connectionSubtype) {
|
||||
for (Long nativeChangeNotifier : mNativeChangeNotifiers) {
|
||||
nativeNotifyMaxBandwidthChanged(nativeChangeNotifier, connectionSubtype);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts all observers of a network connect.
|
||||
*/
|
||||
void notifyObserversOfNetworkConnect(long netId, int connectionType) {
|
||||
for (Long nativeChangeNotifier : mNativeChangeNotifiers) {
|
||||
nativeNotifyOfNetworkConnect(nativeChangeNotifier, netId, connectionType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts all observers of a network soon to be disconnected.
|
||||
*/
|
||||
void notifyObserversOfNetworkSoonToDisconnect(long netId) {
|
||||
for (Long nativeChangeNotifier : mNativeChangeNotifiers) {
|
||||
nativeNotifyOfNetworkSoonToDisconnect(nativeChangeNotifier, netId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts all observers of a network disconnect.
|
||||
*/
|
||||
void notifyObserversOfNetworkDisconnect(long netId) {
|
||||
for (Long nativeChangeNotifier : mNativeChangeNotifiers) {
|
||||
nativeNotifyOfNetworkDisconnect(nativeChangeNotifier, netId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts all observers to purge cached lists of active networks, of any
|
||||
* networks not in the accompanying list of active networks. This is
|
||||
* issued if a period elapsed where disconnected notifications may have
|
||||
* been missed, and acts to keep cached lists of active networks accurate.
|
||||
*/
|
||||
void notifyObserversToPurgeActiveNetworkList(long[] activeNetIds) {
|
||||
for (Long nativeChangeNotifier : mNativeChangeNotifiers) {
|
||||
nativeNotifyPurgeActiveNetworkList(nativeChangeNotifier, activeNetIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an observer for any connection type changes.
|
||||
*/
|
||||
public static void addConnectionTypeObserver(ConnectionTypeObserver observer) {
|
||||
getInstance().addConnectionTypeObserverInternal(observer);
|
||||
}
|
||||
|
||||
private void addConnectionTypeObserverInternal(ConnectionTypeObserver 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the process bound to a network?
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private boolean isProcessBoundToNetworkInternal() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return false;
|
||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return ConnectivityManager.getProcessDefaultNetwork() != null;
|
||||
} else {
|
||||
return mConnectivityManager.getBoundNetworkForProcess() != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the process bound to a network?
|
||||
*/
|
||||
@CalledByNative
|
||||
public static boolean isProcessBoundToNetwork() {
|
||||
return getInstance().isProcessBoundToNetworkInternal();
|
||||
}
|
||||
|
||||
@NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
|
||||
private native void nativeNotifyConnectionTypeChanged(
|
||||
long nativePtr, int newConnectionType, long defaultNetId);
|
||||
|
||||
@NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
|
||||
private native void nativeNotifyMaxBandwidthChanged(long nativePtr, int subType);
|
||||
|
||||
@NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
|
||||
private native void nativeNotifyOfNetworkConnect(
|
||||
long nativePtr, long netId, int connectionType);
|
||||
|
||||
@NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
|
||||
private native void nativeNotifyOfNetworkSoonToDisconnect(long nativePtr, long netId);
|
||||
|
||||
@NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
|
||||
private native void nativeNotifyOfNetworkDisconnect(long nativePtr, long netId);
|
||||
|
||||
@NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
|
||||
private native void nativeNotifyPurgeActiveNetworkList(long nativePtr, long[] activeNetIds);
|
||||
|
||||
// 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 != ConnectionType.CONNECTION_NONE;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
263
net/android/java/src/org/chromium/net/ProxyChangeListener.java
Normal file
263
net/android/java/src/org/chromium/net/ProxyChangeListener.java
Normal file
@@ -0,0 +1,263 @@
|
||||
// Copyright 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 android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.chromium.base.BuildConfig;
|
||||
import org.chromium.base.ContextUtils;
|
||||
import org.chromium.base.annotations.CalledByNative;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
import org.chromium.base.annotations.NativeClassQualifiedName;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 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 final Looper mLooper;
|
||||
private final Handler mHandler;
|
||||
|
||||
private long mNativePtr;
|
||||
private ProxyReceiver mProxyReceiver;
|
||||
private Delegate mDelegate;
|
||||
|
||||
private static class ProxyConfig {
|
||||
public ProxyConfig(String host, int port, String pacUrl, String[] exclusionList) {
|
||||
mHost = host;
|
||||
mPort = port;
|
||||
mPacUrl = pacUrl;
|
||||
mExclusionList = exclusionList;
|
||||
}
|
||||
public final String mHost;
|
||||
public final int mPort;
|
||||
public final String mPacUrl;
|
||||
public final String[] mExclusionList;
|
||||
}
|
||||
|
||||
/**
|
||||
* The delegate for ProxyChangeListener. Use for testing.
|
||||
*/
|
||||
public interface Delegate {
|
||||
public void proxySettingsChanged();
|
||||
}
|
||||
|
||||
private ProxyChangeListener() {
|
||||
mLooper = Looper.myLooper();
|
||||
mHandler = new Handler(mLooper);
|
||||
}
|
||||
|
||||
public static void setEnabled(boolean enabled) {
|
||||
sEnabled = enabled;
|
||||
}
|
||||
|
||||
public void setDelegateForTesting(Delegate delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public static ProxyChangeListener create() {
|
||||
return new ProxyChangeListener();
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public static String getProperty(String property) {
|
||||
return System.getProperty(property);
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public void start(long nativePtr) {
|
||||
assertOnThread();
|
||||
assert mNativePtr == 0;
|
||||
mNativePtr = nativePtr;
|
||||
registerReceiver();
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
public void stop() {
|
||||
assertOnThread();
|
||||
mNativePtr = 0;
|
||||
unregisterReceiver();
|
||||
}
|
||||
|
||||
private class ProxyReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, final Intent intent) {
|
||||
if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
|
||||
runOnThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
proxySettingsChanged(ProxyReceiver.this, extractNewProxy(intent));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract a ProxyConfig object from the supplied Intent's extra data
|
||||
// bundle. The android.net.ProxyProperties class is not exported from
|
||||
// the Android SDK, so we have to use reflection to get at it and invoke
|
||||
// methods on it. If we fail, return an empty proxy config (meaning
|
||||
// 'direct').
|
||||
// TODO(sgurun): once android.net.ProxyInfo is public, rewrite this.
|
||||
private ProxyConfig extractNewProxy(Intent intent) {
|
||||
try {
|
||||
final String getHostName = "getHost";
|
||||
final String getPortName = "getPort";
|
||||
final String getPacFileUrl = "getPacFileUrl";
|
||||
final String getExclusionList = "getExclusionList";
|
||||
String className;
|
||||
String proxyInfo;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
className = "android.net.ProxyProperties";
|
||||
proxyInfo = "proxy";
|
||||
} else {
|
||||
className = "android.net.ProxyInfo";
|
||||
proxyInfo = "android.intent.extra.PROXY_INFO";
|
||||
}
|
||||
|
||||
Object props = intent.getExtras().get(proxyInfo);
|
||||
if (props == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> cls = Class.forName(className);
|
||||
Method getHostMethod = cls.getDeclaredMethod(getHostName);
|
||||
Method getPortMethod = cls.getDeclaredMethod(getPortName);
|
||||
Method getExclusionListMethod = cls.getDeclaredMethod(getExclusionList);
|
||||
|
||||
String host = (String) getHostMethod.invoke(props);
|
||||
int port = (Integer) getPortMethod.invoke(props);
|
||||
|
||||
String[] exclusionList;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
String s = (String) getExclusionListMethod.invoke(props);
|
||||
exclusionList = s.split(",");
|
||||
} else {
|
||||
exclusionList = (String[]) getExclusionListMethod.invoke(props);
|
||||
}
|
||||
// TODO(xunjieli): rewrite this once the API is public.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
||||
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
Method getPacFileUrlMethod = cls.getDeclaredMethod(getPacFileUrl);
|
||||
String pacFileUrl = (String) getPacFileUrlMethod.invoke(props);
|
||||
if (!TextUtils.isEmpty(pacFileUrl)) {
|
||||
return new ProxyConfig(host, port, pacFileUrl, exclusionList);
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Method getPacFileUrlMethod = cls.getDeclaredMethod(getPacFileUrl);
|
||||
Uri pacFileUrl = (Uri) getPacFileUrlMethod.invoke(props);
|
||||
if (!Uri.EMPTY.equals(pacFileUrl)) {
|
||||
return new ProxyConfig(host, port, pacFileUrl.toString(), exclusionList);
|
||||
}
|
||||
}
|
||||
return new ProxyConfig(host, port, null, exclusionList);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
Log.e(TAG, "Using no proxy configuration due to exception:" + ex);
|
||||
return null;
|
||||
} catch (NoSuchMethodException ex) {
|
||||
Log.e(TAG, "Using no proxy configuration due to exception:" + ex);
|
||||
return null;
|
||||
} catch (IllegalAccessException ex) {
|
||||
Log.e(TAG, "Using no proxy configuration due to exception:" + ex);
|
||||
return null;
|
||||
} catch (InvocationTargetException ex) {
|
||||
Log.e(TAG, "Using no proxy configuration due to exception:" + ex);
|
||||
return null;
|
||||
} catch (NullPointerException ex) {
|
||||
Log.e(TAG, "Using no proxy configuration due to exception:" + ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void proxySettingsChanged(ProxyReceiver proxyReceiver, ProxyConfig cfg) {
|
||||
if (!sEnabled
|
||||
// Once execution begins on the correct thread, make sure unregisterReceiver()
|
||||
// hasn't been called in the mean time. Ignore the changed signal if
|
||||
// unregisterReceiver() was called.
|
||||
|| proxyReceiver != mProxyReceiver) {
|
||||
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.
|
||||
if (cfg != null) {
|
||||
nativeProxySettingsChangedTo(mNativePtr, cfg.mHost, cfg.mPort, cfg.mPacUrl,
|
||||
cfg.mExclusionList);
|
||||
} else {
|
||||
nativeProxySettingsChanged(mNativePtr);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerReceiver() {
|
||||
if (mProxyReceiver != null) {
|
||||
return;
|
||||
}
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Proxy.PROXY_CHANGE_ACTION);
|
||||
mProxyReceiver = new ProxyReceiver();
|
||||
ContextUtils.getApplicationContext().registerReceiver(mProxyReceiver, filter);
|
||||
}
|
||||
|
||||
private void unregisterReceiver() {
|
||||
if (mProxyReceiver == null) {
|
||||
return;
|
||||
}
|
||||
ContextUtils.getApplicationContext().unregisterReceiver(mProxyReceiver);
|
||||
mProxyReceiver = null;
|
||||
}
|
||||
|
||||
private boolean onThread() {
|
||||
return mLooper == Looper.myLooper();
|
||||
}
|
||||
|
||||
private void assertOnThread() {
|
||||
if (BuildConfig.DCHECK_IS_ON && !onThread()) {
|
||||
throw new IllegalStateException("Must be called on ProxyChangeListener thread.");
|
||||
}
|
||||
}
|
||||
|
||||
private void runOnThread(Runnable r) {
|
||||
if (onThread()) {
|
||||
r.run();
|
||||
} else {
|
||||
mHandler.post(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See net/proxy/proxy_config_service_android.cc
|
||||
*/
|
||||
@NativeClassQualifiedName("ProxyConfigServiceAndroid::JNIDelegate")
|
||||
private native void nativeProxySettingsChangedTo(long nativePtr,
|
||||
String host,
|
||||
int port,
|
||||
String pacUrl,
|
||||
String[] exclusionList);
|
||||
@NativeClassQualifiedName("ProxyConfigServiceAndroid::JNIDelegate")
|
||||
private native void nativeProxySettingsChanged(long nativePtr);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2015 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;
|
||||
|
||||
/**
|
||||
* Registration policy which make sure that the listener is always registered.
|
||||
*/
|
||||
public class RegistrationPolicyAlwaysRegister
|
||||
extends NetworkChangeNotifierAutoDetect.RegistrationPolicy {
|
||||
@Override
|
||||
protected void init(NetworkChangeNotifierAutoDetect notifier) {
|
||||
super.init(notifier);
|
||||
register();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroy() {}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2015 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.ApplicationState;
|
||||
import org.chromium.base.ApplicationStatus;
|
||||
import org.chromium.base.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* Regsitration policy which depends on the ApplicationState.
|
||||
*/
|
||||
public class RegistrationPolicyApplicationStatus
|
||||
extends NetworkChangeNotifierAutoDetect.RegistrationPolicy
|
||||
implements ApplicationStatus.ApplicationStateListener {
|
||||
private boolean mDestroyed;
|
||||
|
||||
@Override
|
||||
protected void init(NetworkChangeNotifierAutoDetect notifier) {
|
||||
super.init(notifier);
|
||||
ApplicationStatus.registerApplicationStateListener(this);
|
||||
onApplicationStateChange(getApplicationState());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroy() {
|
||||
if (mDestroyed) return;
|
||||
ApplicationStatus.unregisterApplicationStateListener(this);
|
||||
mDestroyed = true;
|
||||
}
|
||||
|
||||
// ApplicationStatus.ApplicationStateListener
|
||||
@Override
|
||||
public void onApplicationStateChange(int newState) {
|
||||
if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES) {
|
||||
register();
|
||||
} else if (newState == ApplicationState.HAS_PAUSED_ACTIVITIES) {
|
||||
unregister();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the activity's status.
|
||||
* @return an {@code int} that is one of {@code ApplicationState.HAS_*_ACTIVITIES}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
int getApplicationState() {
|
||||
return ApplicationStatus.getStateForApplication();
|
||||
}
|
||||
}
|
||||
571
net/android/java/src/org/chromium/net/X509Util.java
Normal file
571
net/android/java/src/org/chromium/net/X509Util.java
Normal file
@@ -0,0 +1,571 @@
|
||||
// Copyright 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.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.http.X509TrustManagerExtensions;
|
||||
import android.os.Build;
|
||||
import android.security.KeyChain;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.chromium.base.ContextUtils;
|
||||
import org.chromium.base.annotations.JNINamespace;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
/**
|
||||
* Utility functions for verifying X.509 certificates.
|
||||
*/
|
||||
@JNINamespace("net")
|
||||
public class X509Util {
|
||||
|
||||
private static final String TAG = "X509Util";
|
||||
|
||||
private static final class TrustStorageListener extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
boolean shouldReloadTrustManager = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (KeyChain.ACTION_KEYCHAIN_CHANGED.equals(intent.getAction())
|
||||
|| KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
|
||||
// TODO(davidben): ACTION_KEYCHAIN_CHANGED indicates client certificates
|
||||
// changed, not the trust store. The two signals within CertDatabase are
|
||||
// identical, so we are reloading more than needed. But note b/36492171.
|
||||
shouldReloadTrustManager = true;
|
||||
} else if (KeyChain.ACTION_KEY_ACCESS_CHANGED.equals(intent.getAction())
|
||||
&& !intent.getBooleanExtra(KeyChain.EXTRA_KEY_ACCESSIBLE, false)) {
|
||||
// We lost access to a client certificate key. Reload all client certificate
|
||||
// state as we are not currently able to forget an individual identity.
|
||||
shouldReloadTrustManager = true;
|
||||
}
|
||||
} else {
|
||||
// Before Android O, KeyChain only emitted a coarse-grained intent. This fires much
|
||||
// more often than it should (https://crbug.com/381912), but there are no APIs to
|
||||
// distinguish the various cases.
|
||||
shouldReloadTrustManager =
|
||||
KeyChain.ACTION_STORAGE_CHANGED.equals(intent.getAction());
|
||||
}
|
||||
|
||||
if (shouldReloadTrustManager) {
|
||||
try {
|
||||
reloadDefaultTrustManager();
|
||||
} catch (CertificateException e) {
|
||||
Log.e(TAG, "Unable to reload the default TrustManager", e);
|
||||
} catch (KeyStoreException e) {
|
||||
Log.e(TAG, "Unable to reload the default TrustManager", e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "Unable to reload the default TrustManager", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that wraps one of X509TrustManager or
|
||||
* X509TrustManagerExtensions to support platforms before the latter was
|
||||
* added.
|
||||
*/
|
||||
private static interface X509TrustManagerImplementation {
|
||||
public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
|
||||
String authType,
|
||||
String host) throws CertificateException;
|
||||
}
|
||||
|
||||
private static final class X509TrustManagerIceCreamSandwich implements
|
||||
X509TrustManagerImplementation {
|
||||
private final X509TrustManager mTrustManager;
|
||||
|
||||
public X509TrustManagerIceCreamSandwich(X509TrustManager trustManager) {
|
||||
mTrustManager = trustManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
|
||||
String authType,
|
||||
String host) throws CertificateException {
|
||||
mTrustManager.checkServerTrusted(chain, authType);
|
||||
return Collections.<X509Certificate>emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class X509TrustManagerJellyBean implements X509TrustManagerImplementation {
|
||||
private final X509TrustManagerExtensions mTrustManagerExtensions;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public X509TrustManagerJellyBean(X509TrustManager trustManager) {
|
||||
mTrustManagerExtensions = new X509TrustManagerExtensions(trustManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("NewApi")
|
||||
public List<X509Certificate> checkServerTrusted(
|
||||
X509Certificate[] chain, String authType, String host) throws CertificateException {
|
||||
// API Level 17: android.net.http.X509TrustManagerExtensions#checkServerTrusted
|
||||
return mTrustManagerExtensions.checkServerTrusted(chain, authType, host);
|
||||
}
|
||||
}
|
||||
|
||||
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 X509TrustManagerImplementation sDefaultTrustManager;
|
||||
|
||||
/**
|
||||
* BroadcastReceiver that listens to change in the system keystore to invalidate certificate
|
||||
* caches.
|
||||
*/
|
||||
private static TrustStorageListener sTrustStorageListener;
|
||||
|
||||
/**
|
||||
* 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 X509TrustManagerImplementation sTestTrustManager;
|
||||
private static KeyStore sTestKeyStore;
|
||||
|
||||
/**
|
||||
* The system key store. This is used to determine whether a trust anchor is a system trust
|
||||
* anchor or user-installed.
|
||||
*/
|
||||
private static KeyStore sSystemKeyStore;
|
||||
|
||||
/**
|
||||
* The directory where system certificates are stored. This is used to determine whether a
|
||||
* trust anchor is a system trust anchor or user-installed. The KeyStore API alone is not
|
||||
* sufficient to efficiently query whether a given X500Principal, PublicKey pair is a trust
|
||||
* anchor.
|
||||
*/
|
||||
private static File sSystemCertificateDirectory;
|
||||
|
||||
/**
|
||||
* An in-memory cache of which trust anchors are system trust roots. This avoids reading and
|
||||
* decoding the root from disk on every verification. Mirrors a similar in-memory cache in
|
||||
* Conscrypt's X509TrustManager implementation.
|
||||
*/
|
||||
private static Set<Pair<X500Principal, PublicKey>> sSystemTrustAnchorCache;
|
||||
|
||||
/**
|
||||
* True if the system key store has been loaded. If the "AndroidCAStore" KeyStore instance
|
||||
* was not found, sSystemKeyStore may be null while sLoadedSystemKeyStore is true.
|
||||
*/
|
||||
private static boolean sLoadedSystemKeyStore;
|
||||
|
||||
/**
|
||||
* Lock object used to synchronize all calls that modify or depend on the trust managers.
|
||||
*/
|
||||
private static final Object sLock = new Object();
|
||||
|
||||
/**
|
||||
* Allow disabling registering the observer and recording histograms for the certificate
|
||||
* changes. Net unit tests do not load native libraries which prevent this to succeed. Moreover,
|
||||
* the system does not allow to interact with the certificate store without user interaction.
|
||||
*/
|
||||
private static boolean sDisableNativeCodeForTest;
|
||||
|
||||
/**
|
||||
* Ensures that the trust managers and certificate factory are initialized.
|
||||
*/
|
||||
private static void ensureInitialized() throws CertificateException,
|
||||
KeyStoreException, NoSuchAlgorithmException {
|
||||
synchronized (sLock) {
|
||||
ensureInitializedLocked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the trust managers and certificate factory are initialized. Must be called with
|
||||
* |sLock| held.
|
||||
*/
|
||||
// FindBugs' static field initialization warnings do not handle methods that are expected to be
|
||||
// called locked.
|
||||
private static void ensureInitializedLocked()
|
||||
throws CertificateException, KeyStoreException, NoSuchAlgorithmException {
|
||||
assert Thread.holdsLock(sLock);
|
||||
|
||||
if (sCertificateFactory == null) {
|
||||
sCertificateFactory = CertificateFactory.getInstance("X.509");
|
||||
}
|
||||
if (sDefaultTrustManager == null) {
|
||||
sDefaultTrustManager = X509Util.createTrustManager(null);
|
||||
}
|
||||
if (!sLoadedSystemKeyStore) {
|
||||
try {
|
||||
sSystemKeyStore = KeyStore.getInstance("AndroidCAStore");
|
||||
try {
|
||||
sSystemKeyStore.load(null);
|
||||
} catch (IOException e) {
|
||||
// No IO operation is attempted.
|
||||
}
|
||||
sSystemCertificateDirectory =
|
||||
new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
|
||||
} catch (KeyStoreException e) {
|
||||
// Could not load AndroidCAStore. Continue anyway; isKnownRoot will always
|
||||
// return false.
|
||||
}
|
||||
if (!sDisableNativeCodeForTest) {
|
||||
nativeRecordCertVerifyCapabilitiesHistogram(sSystemKeyStore != null);
|
||||
}
|
||||
sLoadedSystemKeyStore = true;
|
||||
}
|
||||
if (sSystemTrustAnchorCache == null) {
|
||||
sSystemTrustAnchorCache = new HashSet<Pair<X500Principal, PublicKey>>();
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (!sDisableNativeCodeForTest && sTrustStorageListener == null) {
|
||||
sTrustStorageListener = new TrustStorageListener();
|
||||
IntentFilter filter = new IntentFilter();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
filter.addAction(KeyChain.ACTION_KEYCHAIN_CHANGED);
|
||||
filter.addAction(KeyChain.ACTION_KEY_ACCESS_CHANGED);
|
||||
filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
|
||||
} else {
|
||||
filter.addAction(KeyChain.ACTION_STORAGE_CHANGED);
|
||||
}
|
||||
ContextUtils.getApplicationContext().registerReceiver(sTrustStorageListener, filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a X509TrustManagerImplementation backed up by the given key
|
||||
* store. When null is passed as a key store, system default trust store is
|
||||
* used. Returns null if no created TrustManager was suitable.
|
||||
* @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
|
||||
*/
|
||||
private static X509TrustManagerImplementation 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) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return new X509TrustManagerJellyBean((X509TrustManager) tm);
|
||||
} else {
|
||||
return new X509TrustManagerIceCreamSandwich((X509TrustManager) tm);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
String className = tm.getClass().getName();
|
||||
Log.e(TAG, "Error creating trust manager (" + className + "): " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.e(TAG, "Could not find suitable trust manager");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* After each modification of test key store, trust manager has to be generated again.
|
||||
*/
|
||||
private static void reloadTestTrustManager() throws KeyStoreException,
|
||||
NoSuchAlgorithmException {
|
||||
assert Thread.holdsLock(sLock);
|
||||
|
||||
sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* After each modification by the system of the key store, trust manager has to be regenerated.
|
||||
*/
|
||||
private static void reloadDefaultTrustManager() throws KeyStoreException,
|
||||
NoSuchAlgorithmException, CertificateException {
|
||||
synchronized (sLock) {
|
||||
sDefaultTrustManager = null;
|
||||
sSystemTrustAnchorCache = null;
|
||||
ensureInitializedLocked();
|
||||
}
|
||||
nativeNotifyKeyChainChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final char[] HEX_DIGITS = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'a', 'b', 'c', 'd', 'e', 'f',
|
||||
};
|
||||
|
||||
private static String hashPrincipal(X500Principal principal) throws NoSuchAlgorithmException {
|
||||
// Android hashes a principal as the first four bytes of its MD5 digest, encoded in
|
||||
// lowercase hex and reversed. Verified in 4.2, 4.3, and 4.4.
|
||||
byte[] digest = MessageDigest.getInstance("MD5").digest(principal.getEncoded());
|
||||
char[] hexChars = new char[8];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
hexChars[2 * i] = HEX_DIGITS[(digest[3 - i] >> 4) & 0xf];
|
||||
hexChars[2 * i + 1] = HEX_DIGITS[digest[3 - i] & 0xf];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
private static boolean isKnownRoot(X509Certificate root)
|
||||
throws NoSuchAlgorithmException, KeyStoreException {
|
||||
assert Thread.holdsLock(sLock);
|
||||
|
||||
// Could not find the system key store. Conservatively report false.
|
||||
if (sSystemKeyStore == null) return false;
|
||||
|
||||
// Check the in-memory cache first; avoid decoding the anchor from disk
|
||||
// if it has been seen before.
|
||||
Pair<X500Principal, PublicKey> key = new Pair<X500Principal, PublicKey>(
|
||||
root.getSubjectX500Principal(), root.getPublicKey());
|
||||
|
||||
if (sSystemTrustAnchorCache.contains(key)) return true;
|
||||
|
||||
// Note: It is not sufficient to call sSystemKeyStore.getCertificiateAlias. If the server
|
||||
// supplies a copy of a trust anchor, X509TrustManagerExtensions returns the server's
|
||||
// version rather than the system one. getCertificiateAlias will then fail to find an anchor
|
||||
// name. This is fixed upstream in https://android-review.googlesource.com/#/c/91605/
|
||||
//
|
||||
// TODO(davidben): When the change trickles into an Android release, query sSystemKeyStore
|
||||
// directly.
|
||||
|
||||
// System trust anchors are stored under a hash of the principal. In case of collisions,
|
||||
// a number is appended.
|
||||
String hash = hashPrincipal(root.getSubjectX500Principal());
|
||||
for (int i = 0; true; i++) {
|
||||
String alias = hash + '.' + i;
|
||||
if (!new File(sSystemCertificateDirectory, alias).exists()) break;
|
||||
|
||||
Certificate anchor = sSystemKeyStore.getCertificate("system:" + alias);
|
||||
// It is possible for this to return null if the user deleted a trust anchor. In
|
||||
// that case, the certificate remains in the system directory but is also added to
|
||||
// another file. Continue iterating as there may be further collisions after the
|
||||
// deleted anchor.
|
||||
if (anchor == null) continue;
|
||||
|
||||
if (!(anchor instanceof X509Certificate)) {
|
||||
// This should never happen.
|
||||
String className = anchor.getClass().getName();
|
||||
Log.e(TAG, "Anchor " + alias + " not an X509Certificate: " + className);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the subject and public key match, this is a system root.
|
||||
X509Certificate anchorX509 = (X509Certificate) anchor;
|
||||
if (root.getSubjectX500Principal().equals(anchorX509.getSubjectX500Principal())
|
||||
&& root.getPublicKey().equals(anchorX509.getPublicKey())) {
|
||||
sSystemTrustAnchorCache.add(key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 AndroidCertVerifyResult verifyServerCertificates(byte[][] certChain,
|
||||
String authType,
|
||||
String host)
|
||||
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|=" + Arrays.deepToString(certChain));
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
ensureInitialized();
|
||||
} catch (CertificateException e) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.FAILED);
|
||||
}
|
||||
|
||||
List<X509Certificate> serverCertificatesList = new ArrayList<X509Certificate>();
|
||||
try {
|
||||
serverCertificatesList.add(createCertificateFromBytes(certChain[0]));
|
||||
} catch (CertificateException e) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.UNABLE_TO_PARSE);
|
||||
}
|
||||
for (int i = 1; i < certChain.length; ++i) {
|
||||
try {
|
||||
serverCertificatesList.add(createCertificateFromBytes(certChain[i]));
|
||||
} catch (CertificateException e) {
|
||||
Log.w(TAG, "intermediate " + i + " failed parsing");
|
||||
}
|
||||
}
|
||||
X509Certificate[] serverCertificates =
|
||||
serverCertificatesList.toArray(new X509Certificate[serverCertificatesList.size()]);
|
||||
|
||||
// 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 new AndroidCertVerifyResult(
|
||||
CertVerifyStatusAndroid.INCORRECT_KEY_USAGE);
|
||||
}
|
||||
} catch (CertificateExpiredException e) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.EXPIRED);
|
||||
} catch (CertificateNotYetValidException e) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.NOT_YET_VALID);
|
||||
} catch (CertificateException e) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.FAILED);
|
||||
}
|
||||
|
||||
synchronized (sLock) {
|
||||
// If no trust manager was found, fail without crashing on the null pointer.
|
||||
if (sDefaultTrustManager == null) {
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.FAILED);
|
||||
}
|
||||
|
||||
List<X509Certificate> verifiedChain;
|
||||
try {
|
||||
verifiedChain = sDefaultTrustManager.checkServerTrusted(serverCertificates,
|
||||
authType, host);
|
||||
} catch (CertificateException eDefaultManager) {
|
||||
try {
|
||||
verifiedChain = sTestTrustManager.checkServerTrusted(serverCertificates,
|
||||
authType, host);
|
||||
} 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 new AndroidCertVerifyResult(
|
||||
CertVerifyStatusAndroid.NO_TRUSTED_ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isIssuedByKnownRoot = false;
|
||||
if (verifiedChain.size() > 0) {
|
||||
X509Certificate root = verifiedChain.get(verifiedChain.size() - 1);
|
||||
isIssuedByKnownRoot = isKnownRoot(root);
|
||||
}
|
||||
|
||||
return new AndroidCertVerifyResult(CertVerifyStatusAndroid.OK,
|
||||
isIssuedByKnownRoot, verifiedChain);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setDisableNativeCodeForTest(boolean disabled) {
|
||||
sDisableNativeCodeForTest = disabled;
|
||||
}
|
||||
/**
|
||||
* Notify the native net::CertDatabase instance that the system database has been updated.
|
||||
*/
|
||||
private static native void nativeNotifyKeyChainChanged();
|
||||
|
||||
/**
|
||||
* Record histograms on the platform's certificate verification capabilities.
|
||||
*/
|
||||
private static native void nativeRecordCertVerifyCapabilitiesHistogram(
|
||||
boolean foundSystemTrustRoots);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user