Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add security policies for checking device owner/profile owner. #9428

Merged
merged 5 commits into from Sep 7, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions binder/build.gradle
Expand Up @@ -23,14 +23,14 @@ android {
}
}
}
compileSdkVersion 29
compileSdkVersion 30
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down
70 changes: 70 additions & 0 deletions binder/src/main/java/io/grpc/binder/SecurityPolicies.java
Expand Up @@ -17,11 +17,14 @@
package io.grpc.binder;

import android.annotation.SuppressLint;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Process;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
Expand Down Expand Up @@ -173,6 +176,50 @@ public Status checkAuthorization(int uid) {
};
}

/**
* Creates {@link SecurityPolicy} which checks if the app is a device owner app. See
* {@link DevicePolicyManager}.
*/
public static SecurityPolicy isDeviceOwner(Context applicationContext) {
DevicePolicyManager devicePolicyManager =
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
return anyPackageWithUidSatisfies(
applicationContext,
pkg -> VERSION.SDK_INT >= 18 && devicePolicyManager.isDeviceOwnerApp(pkg),
"Rejected by device owner policy. No packages found for UID.",
"Rejected by device owner policy");
}

/**
* Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See
* {@link DevicePolicyManager}.
*/
public static SecurityPolicy isProfileOwner(Context applicationContext) {
DevicePolicyManager devicePolicyManager =
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
return anyPackageWithUidSatisfies(
applicationContext,
pkg -> VERSION.SDK_INT >= 21 && devicePolicyManager.isProfileOwnerApp(pkg),
"Rejected by profile owner policy. No packages found for UID.",
"Rejected by profile owner policy");
}

/**
* Creates {@link SecurityPolicy} which checks if the app is a profile owner app on an
* organization-owned device. See {@link DevicePolicyManager}.
*/
public static SecurityPolicy isProfileOwnerOnOrganizationOwnedDevice(Context applicationContext) {
DevicePolicyManager devicePolicyManager =
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
return anyPackageWithUidSatisfies(
applicationContext,
pkg -> VERSION.SDK_INT >= 30
&& devicePolicyManager.isProfileOwnerApp(pkg)
&& devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(),
"Rejected by profile owner on organization-owned device policy. No packages found for UID.",
"Rejected by profile owner on organization-owned device policy");
}

private static Status checkUidSignature(
PackageManager packageManager,
int uid,
Expand Down Expand Up @@ -406,6 +453,29 @@ private static Status checkPermissions(
return Status.OK;
}

private static SecurityPolicy anyPackageWithUidSatisfies(
Context applicationContext,
Predicate<String> condition,
String errorMessageForNoPackages,
String errorMessageForDenied) {
return new SecurityPolicy() {
@Override
public Status checkAuthorization(int uid) {
String[] packages = applicationContext.getPackageManager().getPackagesForUid(uid);
if (packages == null || packages.length == 0) {
return Status.UNAUTHENTICATED.withDescription(errorMessageForNoPackages);
}

for (String pkg : packages) {
if (condition.apply(pkg)) {
return Status.OK;
}
}
return Status.PERMISSION_DENIED.withDescription(errorMessageForDenied);
}
};
}

/**
* Checks if the SHA-256 hash of the {@code signature} matches one of the {@code
* expectedSignatureSha256Hashes}.
Expand Down
174 changes: 174 additions & 0 deletions binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
Expand Up @@ -24,10 +24,15 @@
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;

import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Process;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
Expand All @@ -40,6 +45,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
public final class SecurityPoliciesTest {
Expand All @@ -59,13 +65,16 @@ public final class SecurityPoliciesTest {

private Context appContext;
private PackageManager packageManager;
private DevicePolicyManager devicePolicyManager;

private SecurityPolicy policy;

@Before
public void setUp() {
appContext = ApplicationProvider.getApplicationContext();
packageManager = appContext.getPackageManager();
devicePolicyManager =
(DevicePolicyManager) appContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
}

@SuppressWarnings("deprecation")
Expand Down Expand Up @@ -323,6 +332,171 @@ public void testHasPermissions_failsIfPackageDoesNotHavePermissions() throws Exc
.contains(OTHER_UID_PACKAGE_NAME);
}

@Test
@Config(sdk = 18)
public void testIsDeviceOwner_succeedsForDeviceOwner() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);
shadowOf(devicePolicyManager)
.setDeviceOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));

policy = SecurityPolicies.isDeviceOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
}

@Test
@Config(sdk = 18)
public void testIsDeviceOwner_failsForNotDeviceOwner() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy = SecurityPolicies.isDeviceOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
}

@Test
@Config(sdk = 18)
public void testIsDeviceOwner_failsWhenNoPackagesForUid() throws Exception {
policy = SecurityPolicies.isDeviceOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
}

@Test
@Config(sdk = 17)
public void testIsDeviceOwner_failsForSdkLevelTooLow() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy = SecurityPolicies.isDeviceOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
}

@Test
@Config(sdk = 21)
public void testIsProfileOwner_succeedsForProfileOwner() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);
shadowOf(devicePolicyManager)
.setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));

policy = SecurityPolicies.isProfileOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
}

@Test
@Config(sdk = 21)
public void testIsProfileOwner_failsForNotProfileOwner() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy = SecurityPolicies.isProfileOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
}

@Test
@Config(sdk = 21)
public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception {
policy = SecurityPolicies.isProfileOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
}

@Test
@Config(sdk = 19)
public void testIsProfileOwner_failsForSdkLevelTooLow() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy = SecurityPolicies.isProfileOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
}

@Test
@Config(sdk = 30)
public void testIsProfileOwnerOnOrgOwned_succeedsForProfileOwnerOnOrgOwned() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);
shadowOf(devicePolicyManager)
.setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(true);

policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());

}

@Test
@Config(sdk = 30)
public void testIsProfileOwnerOnOrgOwned_failsForProfileOwnerOnNonOrgOwned() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);
shadowOf(devicePolicyManager)
.setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(false);

policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
}

@Test
@Config(sdk = 21)
public void testIsProfileOwnerOnOrgOwned_failsForNotProfileOwner() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
}

@Test
@Config(sdk = 21)
public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception {
policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
}

@Test
@Config(sdk = 29)
public void testIsProfileOwnerOnOrgOwned_failsForSdkLevelTooLow() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();

installPackages(OTHER_UID, info);

policy = SecurityPolicies.isProfileOwner(appContext);

assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
}

private static PackageInfoBuilder newBuilder() {
return new PackageInfoBuilder();
}
Expand Down