Import chromium-64.0.3282.140

This commit is contained in:
klzgrad
2018-02-02 05:49:39 -05:00
commit 86b64329f6
19589 changed files with 4029211 additions and 0 deletions

View 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"
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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;
}
}
}

View 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();
}
}
}

View File

@@ -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;
}
}

View 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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View 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;
}
}
}

View 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

View 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);
}

View File

@@ -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() {}
}

View File

@@ -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();
}
}

View 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);
}