Add network consent check before connecting to RKP server.
Some restricted regions require a check for network consent before
making a network call. We assume that using GMS application gives a
consent for making network calls since GMS by default is dependent on
network connection with Google.
Test: atest RkpdAppUnitTests
Bug: 298881366
Change-Id: I6fedbc6b400735fa612ce33d3253df16f5f7aebd
diff --git a/app/src/com/android/rkpdapp/interfaces/ServerInterface.java b/app/src/com/android/rkpdapp/interfaces/ServerInterface.java
index 7ed18b2..f11c527 100644
--- a/app/src/com/android/rkpdapp/interfaces/ServerInterface.java
+++ b/app/src/com/android/rkpdapp/interfaces/ServerInterface.java
@@ -17,6 +17,7 @@
package com.android.rkpdapp.interfaces;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.TrafficStats;
@@ -69,6 +70,9 @@
private static final String CERTIFICATE_SIGNING_URL = ":signCertificates";
private static final String CHALLENGE_PARAMETER = "challenge";
private static final String REQUEST_ID_PARAMETER = "request_id";
+ private static final String GMS_PACKAGE = "com.google.android.gms";
+ private static final String CHINA_GMS_FEATURE = "cn.google.services";
+
private final Context mContext;
private final boolean mIsAsync;
@@ -230,6 +234,12 @@
throw new RkpdException(RkpdException.ErrorCode.NO_NETWORK_CONNECTIVITY,
"No network detected.");
}
+ // Since fetchGeek would be the first call for any sort of provisioning, we are okay
+ // checking network consent here.
+ if (!assumeNetworkConsent(mContext)) {
+ throw new RkpdException(RkpdException.ErrorCode.NETWORK_COMMUNICATION_ERROR,
+ "Network communication consent not provided. Need to enable GMSCore app.");
+ }
byte[] input = CborUtils.buildProvisioningInfo(mContext);
byte[] cborBytes =
connectAndGetData(metrics, generateFetchGeekUrl(), input, Operation.FETCH_GEEK);
@@ -343,6 +353,29 @@
return new String(bytes, charset);
}
+ /**
+ * Checks whether GMSCore is installed and enabled for restricted regions.
+ * This lets us assume that user has consented to connecting to Google
+ * servers to provide attestation service.
+ * For all other regions, we assume consent by default since this is an
+ * Android OS-level application.
+ *
+ * @return True if user consent can be assumed else false.
+ */
+ @VisibleForTesting
+ public static boolean assumeNetworkConsent(Context context) {
+ PackageManager pm = context.getPackageManager();
+ if (pm.hasSystemFeature(CHINA_GMS_FEATURE)) {
+ // For china GMS, we can simply check whether GMS package is installed and enabled.
+ try {
+ return pm.getApplicationInfo(GMS_PACKAGE, 0).enabled;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private static Charset getCharsetFromContentTypeHeader(String contentType) {
final String[] contentTypeParts = contentType.split(";");
if (contentTypeParts.length != 2) {
diff --git a/app/tests/unit/src/com/android/rkpdapp/unittest/ServerInterfaceTest.java b/app/tests/unit/src/com/android/rkpdapp/unittest/ServerInterfaceTest.java
index 6f85dba..ed1f64a 100644
--- a/app/tests/unit/src/com/android/rkpdapp/unittest/ServerInterfaceTest.java
+++ b/app/tests/unit/src/com/android/rkpdapp/unittest/ServerInterfaceTest.java
@@ -20,6 +20,8 @@
import static com.google.common.truth.Truth.assertWithMessage;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.util.Base64;
@@ -410,6 +412,35 @@
ServerInterface.SYNC_CONNECT_TIMEOUT_OPEN_MS);
}
+ @Test
+ public void testConnectionConsent() throws Exception {
+ String cnGmsFeature = "cn.google.services";
+ PackageManager mockedPackageManager = Mockito.mock(PackageManager.class);
+ Context mockedContext = Mockito.mock(Context.class);
+ ApplicationInfo fakeApplicationInfo = new ApplicationInfo();
+
+ Mockito.when(mockedContext.getPackageManager()).thenReturn(mockedPackageManager);
+ Mockito.when(mockedPackageManager.hasSystemFeature(cnGmsFeature)).thenReturn(true);
+ Mockito.when(mockedPackageManager.getApplicationInfo(Mockito.any(), Mockito.eq(0)))
+ .thenReturn(fakeApplicationInfo);
+
+ fakeApplicationInfo.enabled = false;
+ assertThat(ServerInterface.assumeNetworkConsent(mockedContext)).isFalse();
+
+ fakeApplicationInfo.enabled = true;
+ assertThat(ServerInterface.assumeNetworkConsent(mockedContext)).isTrue();
+
+ Mockito.when(mockedPackageManager.getApplicationInfo(Mockito.any(), Mockito.eq(0)))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ assertThat(ServerInterface.assumeNetworkConsent(mockedContext)).isFalse();
+
+ Mockito.when(mockedPackageManager.hasSystemFeature(cnGmsFeature)).thenReturn(false);
+ assertThat(ServerInterface.assumeNetworkConsent(mockedContext)).isTrue();
+
+ fakeApplicationInfo.enabled = false;
+ assertThat(ServerInterface.assumeNetworkConsent(mockedContext)).isTrue();
+ }
+
private void mockConnectivityFailure(ConnectivityState state) {
ConnectivityManager mockedConnectivityManager = Mockito.mock(ConnectivityManager.class);