Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions library/java/org/chromium/net/AndroidCertVerifyResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.chromium.net;

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.
*/
public final 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();
}

// TODO(stefanoduo): Hook envoy-mobile JNI.
//@CalledByNative
public int getStatus() { return mStatus; }

// TODO(stefanoduo): Hook envoy-mobile JNI.
//@CalledByNative
public boolean isIssuedByKnownRoot() { return mIsIssuedByKnownRoot; }

// TODO(stefanoduo): Hook envoy-mobile JNI.
//@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;
}
}
97 changes: 97 additions & 0 deletions library/java/org/chromium/net/AndroidNetworkLibrary.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.chromium.net;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
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.TransportInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.telephony.TelephonyManager;
import android.util.Log;

import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;

import java.io.FileDescriptor;
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.
*/
public final class AndroidNetworkLibrary {
private static final String TAG = "AndroidNetworkLibrary";

/**
* 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.
*/
// TODO(stefanoduo): Hook envoy-mobile JNI.
//@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.
*/
// TODO(stefanoduo): Hook envoy-mobile JNI.
//@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.
*/
// TODO(stefanoduo): Hook envoy-mobile JNI.
//@CalledByNativeUnchecked
public static void clearTestRootCertificates()
throws NoSuchAlgorithmException, CertificateException, KeyStoreException {
X509Util.clearTestRootCertificates();
}
}
43 changes: 43 additions & 0 deletions library/java/org/chromium/net/CertVerifyStatusAndroid.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.chromium.net;

import androidx.annotation.IntDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@IntDef({CertVerifyStatusAndroid.OK, CertVerifyStatusAndroid.FAILED,
CertVerifyStatusAndroid.NO_TRUSTED_ROOT, CertVerifyStatusAndroid.EXPIRED,
CertVerifyStatusAndroid.NOT_YET_VALID, CertVerifyStatusAndroid.UNABLE_TO_PARSE,
CertVerifyStatusAndroid.INCORRECT_KEY_USAGE})
@Retention(RetentionPolicy.SOURCE)
public @interface CertVerifyStatusAndroid {
/**
* Certificate is trusted.
*/
int OK = 0;
/**
* Certificate verification could not be conducted.
*/
int FAILED = -1;
/**
* Certificate is not trusted due to non-trusted root of the certificate chain.
*/
int NO_TRUSTED_ROOT = -2;
/**
* Certificate is not trusted because it has expired.
*/
int EXPIRED = -3;
/**
* Certificate is not trusted because it is not valid yet.
*/
int NOT_YET_VALID = -4;
/**
* Certificate is not trusted because it could not be parsed.
*/
int UNABLE_TO_PARSE = -5;
/**
* Certificate is not trusted because it has an extendedKeyUsage field, but its value is not
* correct for a web server.
*/
int INCORRECT_KEY_USAGE = -6;
}
166 changes: 166 additions & 0 deletions library/java/org/chromium/net/ContextUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package org.chromium.net;

import android.app.Activity;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Handler;
import android.os.Process;
import android.preference.PreferenceManager;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

/**
* This class provides Android application context related utility methods.
*/
public final class ContextUtils {
private static final String TAG = "ContextUtils";
private static Context sApplicationContext;

private static boolean sSdkSandboxProcess;

/**
* Flag for {@link Context#registerReceiver}: The receiver can receive broadcasts from other
* Apps. Has the same behavior as marking a statically registered receiver with "exported=true".
*
* TODO(mthiesse): Move to ApiHelperForT when we build against T SDK.
*/
public static final int RECEIVER_EXPORTED = 0x2;
public static final int RECEIVER_NOT_EXPORTED = 0x4;

/**
* Initialization-on-demand holder. This exists for thread-safe lazy initialization.
*/
private static class Holder {
// Not final for tests.
private static SharedPreferences sSharedPreferences = fetchAppSharedPreferences();
}

/**
* Get the Android application context.
*
* Under normal circumstances there is only one application context in a process, so it's safe
* to treat this as a global. In WebView it's possible for more than one app using WebView to be
* running in a single process, but this mechanism is rarely used and this is not the only
* problem in that scenario, so we don't currently forbid using it as a global.
*
* Do not downcast the context returned by this method to Application (or any subclass). It may
* not be an Application object; it may be wrapped in a ContextWrapper. The only assumption you
* may make is that it is a Context whose lifetime is the same as the lifetime of the process.
*/
public static Context getApplicationContext() { return sApplicationContext; }

/**
* Initializes the java application context.
*
* This should be called exactly once early on during startup, before native is loaded and
* before any other clients make use of the application context through this class.
*
* @param appContext The application context.
*/
public static void initApplicationContext(Context appContext) {
// Conceding that occasionally in tests, native is loaded before the browser process is
// started, in which case the browser process re-sets the application context.
assert sApplicationContext == null || sApplicationContext == appContext ||
((ContextWrapper)sApplicationContext).getBaseContext() == appContext;
initJavaSideApplicationContext(appContext);
}

/**
* Only called by the static holder class and tests.
*
* @return The application-wide shared preferences.
*/
@SuppressWarnings("DefaultSharedPreferencesCheck")
private static SharedPreferences fetchAppSharedPreferences() {
// This may need to create the prefs directory if we've never used shared prefs before, so
// allow disk writes. This is rare but can happen if code used early in startup reads prefs.
try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
return PreferenceManager.getDefaultSharedPreferences(sApplicationContext);
}
}

/**
* This is used to ensure that we always use the application context to fetch the default shared
* preferences. This avoids needless I/O for android N and above. It also makes it clear that
* the app-wide shared preference is desired, rather than the potentially context-specific one.
*
* @return application-wide shared preferences.
*/
public static SharedPreferences getAppSharedPreferences() { return Holder.sSharedPreferences; }

/**
* Occasionally tests cannot ensure the application context doesn't change between tests (junit)
* and sometimes specific tests has its own special needs, initApplicationContext should be used
* as much as possible, but this method can be used to override it.
*
* @param appContext The new application context.
*/
@VisibleForTesting
public static void initApplicationContextForTests(Context appContext) {
initJavaSideApplicationContext(appContext);
Holder.sSharedPreferences = fetchAppSharedPreferences();
}

/**
* Tests that use the applicationContext may unintentionally use the Context
* set by a previously run test.
*/
@VisibleForTesting
public static void clearApplicationContextForTests() {
sApplicationContext = null;
Holder.sSharedPreferences = null;
}

private static void initJavaSideApplicationContext(Context appContext) {
assert appContext != null;
// Guard against anyone trying to downcast.
if (appContext instanceof Application) {
appContext = new ContextWrapper(appContext);
}
sApplicationContext = appContext;
}

/**
* As to Exported V.S. NonExported receiver, please refer to
* https://developer.android.com/reference/android/content/Context#registerReceiver(android.content.BroadcastReceiver,%20android.content.IntentFilter,%20int)
*/
public static Intent registerExportedBroadcastReceiver(Context context,
BroadcastReceiver receiver,
IntentFilter filter, String permission) {
return registerBroadcastReceiver(context, receiver, filter, permission, /*scheduler=*/null,
RECEIVER_EXPORTED);
}

public static Intent registerNonExportedBroadcastReceiver(Context context,
BroadcastReceiver receiver,
IntentFilter filter) {
return registerBroadcastReceiver(context, receiver, filter, /*permission=*/null,
/*scheduler=*/null, RECEIVER_NOT_EXPORTED);
}

public static Intent registerNonExportedBroadcastReceiver(Context context,
BroadcastReceiver receiver,
IntentFilter filter,
Handler scheduler) {
return registerBroadcastReceiver(context, receiver, filter, /*permission=*/null, scheduler,
RECEIVER_NOT_EXPORTED);
}

private static Intent registerBroadcastReceiver(Context context, BroadcastReceiver receiver,
IntentFilter filter, String permission,
Handler scheduler, int flags) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return context.registerReceiver(receiver, filter, permission, scheduler, flags);
} else {
return context.registerReceiver(receiver, filter, permission, scheduler);
}
}
}
Loading