Revert "binder: SecurityPolicy updates. (#8632)"

This reverts commit 997592192b.

Hashing is a Beta API in Guava, so we can't use it as-is.
This commit is contained in:
Eric Anderson 2021-10-28 12:55:47 -07:00
parent 997592192b
commit ee395e0e43
4 changed files with 0 additions and 337 deletions

View File

@ -16,21 +16,9 @@
package io.grpc.binder;
import android.annotation.SuppressLint;
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.Process;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import io.grpc.ExperimentalApi;
import io.grpc.Status;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.CheckReturnValue;
/** Static factory methods for creating standard security policies. */
@ -39,7 +27,6 @@ import javax.annotation.CheckReturnValue;
public final class SecurityPolicies {
private static final int MY_UID = Process.myUid();
private static final int SHA_256_BYTES_LENGTH = 32;
private SecurityPolicies() {}
@ -68,155 +55,4 @@ public final class SecurityPolicies {
}
};
}
/**
* Creates {@link SecurityPolicy} which checks if the SHA-256 hash of the package signature
* matches {@code requiredSignatureSha256Hash}.
*
* @param packageName the package name of the allowed package.
* @param requiredSignatureSha256Hash the SHA-256 digest of the signature of the allowed package.
* @throws NullPointerException if any of the inputs are {@code null}.
* @throws IllegalArgumentException if {@code requiredSignatureSha256Hash} is not of length 32.
*/
public static SecurityPolicy hasSignatureSha256Hash(
PackageManager packageManager, String packageName, byte[] requiredSignatureSha256Hash) {
return oneOfSignatureSha256Hash(
packageManager, packageName, ImmutableList.of(requiredSignatureSha256Hash));
}
/**
* Creates {@link SecurityPolicy} which checks if the SHA-256 hash of the package signature
* matches any of {@code requiredSignatureSha256Hashes}.
*
* @param packageName the package name of the allowed package.
* @param requiredSignatureSha256Hashes the SHA-256 digests of the signatures of the allowed
* package.
* @throws NullPointerException if any of the inputs are {@code null}.
* @throws IllegalArgumentException if {@code requiredSignatureSha256Hashes} is empty, or if any
* of the {@code requiredSignatureSha256Hashes} are not of length 32.
*/
public static SecurityPolicy oneOfSignatureSha256Hash(
PackageManager packageManager,
String packageName,
Collection<byte[]> requiredSignatureSha256Hashes) {
Preconditions.checkNotNull(packageManager, "packageManager");
Preconditions.checkNotNull(packageName, "packageName");
Preconditions.checkNotNull(requiredSignatureSha256Hashes, "requiredSignatureSha256Hashes");
Preconditions.checkArgument(!requiredSignatureSha256Hashes.isEmpty(),
"requiredSignatureSha256Hashes");
ImmutableList<byte[]> requiredSignatureSha256HashesImmutable =
ImmutableList.copyOf(requiredSignatureSha256Hashes);
for (byte[] requiredSignatureSha256Hash : requiredSignatureSha256HashesImmutable) {
Preconditions.checkNotNull(requiredSignatureSha256Hash);
Preconditions.checkArgument(requiredSignatureSha256Hash.length == SHA_256_BYTES_LENGTH);
}
return new SecurityPolicy() {
@Override
public Status checkAuthorization(int uid) {
return checkUidSignatureSha256(
packageManager, uid, packageName, requiredSignatureSha256HashesImmutable);
}
};
}
private static Status checkUidSignatureSha256(
PackageManager packageManager,
int uid,
String packageName,
ImmutableList<byte[]> requiredSignatureSha256Hashes) {
String[] packages = packageManager.getPackagesForUid(uid);
if (packages == null) {
return Status.UNAUTHENTICATED.withDescription(
"Rejected by (SHA-256 hash signature check) security policy");
}
boolean packageNameMatched = false;
for (String pkg : packages) {
if (!packageName.equals(pkg)) {
continue;
}
packageNameMatched = true;
if (checkPackageSignatureSha256(
packageManager,
pkg,
requiredSignatureSha256Hashes)) {
return Status.OK;
}
}
return Status.PERMISSION_DENIED.withDescription(
"Rejected by (SHA-256 hash signature check) security policy. Package name matched: "
+ packageNameMatched);
}
/**
* Checks if the signature of {@code packageName} matches on of the given sha256 hashes.
*
* @param packageName the package to be checked
* @param requiredSignatureSha256Hashes a list of hashes.
* @return {@code true} if {@code packageName} has a signature matches one of the hashes.
*/
@SuppressWarnings("deprecation") // For PackageInfo.signatures
@SuppressLint("PackageManagerGetSignatures") // We only allow 1 signature.
private static boolean checkPackageSignatureSha256(
PackageManager packageManager,
String packageName,
ImmutableList<byte[]> requiredSignatureSha256Hashes) {
PackageInfo packageInfo;
try {
if (Build.VERSION.SDK_INT >= 28) {
packageInfo =
packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES);
if (packageInfo.signingInfo == null) {
return false;
}
Signature[] signatures =
packageInfo.signingInfo.hasMultipleSigners()
? packageInfo.signingInfo.getApkContentsSigners()
: packageInfo.signingInfo.getSigningCertificateHistory();
for (Signature signature : signatures) {
if (checkSignatureSha256HashesMatch(signature, requiredSignatureSha256Hashes)) {
return true;
}
}
} else {
packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
if (packageInfo.signatures == null || packageInfo.signatures.length != 1) {
// Reject multiply-signed apks because of b/13678484
// (See PackageManagerGetSignatures supression above).
return false;
}
if (checkSignatureSha256HashesMatch(
packageInfo.signatures[0],
requiredSignatureSha256Hashes)) {
return true;
}
}
} catch (NameNotFoundException nnfe) {
return false;
}
return false;
}
/**
* Checks if the SHA-256 hash of the {@code signature} matches one of the {@code
* expectedSignatureSha256Hashes}.
*/
private static boolean checkSignatureSha256HashesMatch(
Signature signature, List<byte[]> expectedSignatureSha256Hashes) {
byte[] signatureHash = getSha256Hash(signature);
for (byte[] hash : expectedSignatureSha256Hashes) {
if (Arrays.equals(hash, signatureHash)) {
return true;
}
}
return false;
}
/** Returns SHA-256 hash of the provided signature. */
private static byte[] getSha256Hash(Signature signature) {
return Hashing.sha256().hashBytes(signature.toByteArray()).asBytes();
}
}

View File

@ -23,11 +23,6 @@ import javax.annotation.CheckReturnValue;
/**
* Decides whether a given Android UID is authorized to access some resource.
*
* While it's possible to extend this class to define your own policy, it's strongly
* recommended that you only use the policies provided by the {@link SecurityPolicies} or
* {@link UntrustedSecurityPolicies} classes. Implementing your own security policy requires
* significant care, and an understanding of the details and pitfalls of Android security.
*
* <p><b>IMPORTANT</b> For any concrete extensions of this class, it's assumed that the
* authorization status of a given UID will <b>not</b> change as long as a process with that UID is
* alive.

View File

@ -1,47 +0,0 @@
/*
* Copyright 2021 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.binder;
import io.grpc.ExperimentalApi;
import io.grpc.Status;
import javax.annotation.CheckReturnValue;
/**
* Static factory methods for creating untrusted security policies.
*/
@CheckReturnValue
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
public final class UntrustedSecurityPolicies {
private UntrustedSecurityPolicies() {}
/**
* Return a security policy which allows any peer on device.
* Servers should only use this policy if they intend to expose
* a service to all applications on device.
* Clients should only use this policy if they don't need to trust the
* application they're connecting to.
*/
public static SecurityPolicy untrustedPublic() {
return new SecurityPolicy() {
@Override
public Status checkAuthorization(int uid) {
return Status.OK;
}
};
}
}

View File

@ -17,68 +17,22 @@
package io.grpc.binder;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Process;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import io.grpc.Status;
import io.grpc.binder.SecurityPolicy;
import java.util.HashSet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public final class SecurityPoliciesTest {
private static final int MY_UID = Process.myUid();
private static final int OTHER_UID = MY_UID + 1;
private static final int OTHER_UID_SAME_SIGNATURE = MY_UID + 2;
private static final int OTHER_UID_NO_SIGNATURE = MY_UID + 3;
private static final String PERMISSION_DENIED_REASONS = "some reasons";
private static final String SIG1 = "1234";
private static final String SIG2 = "4321";
private static final String OTHER_UID_PACKAGE_NAME = "other.package";
private static final String OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME = "other.package.samesignature";
private static final String OTHER_UID_NO_SIGNATURE_PACKAGE_NAME = "other.package.nosignature";
private Context appContext;
private PackageManager packageManager;
private SecurityPolicy policy;
@Before
public void setUp() {
appContext = ApplicationProvider.getApplicationContext();
packageManager = appContext.getPackageManager();
installPackage(MY_UID, appContext.getPackageName(), SIG1);
installPackage(OTHER_UID, OTHER_UID_PACKAGE_NAME, SIG2);
installPackage(OTHER_UID_SAME_SIGNATURE, OTHER_UID_SAME_SIGNATURE_PACKAGE_NAME, SIG1);
installPackage(OTHER_UID_NO_SIGNATURE, OTHER_UID_NO_SIGNATURE_PACKAGE_NAME);
}
@SuppressWarnings("deprecation")
private void installPackage(int uid, String packageName, String... signatures) {
PackageInfo info = new PackageInfo();
info.packageName = packageName;
info.signatures = new Signature[signatures.length];
for (int i = 0; i < signatures.length; i++) {
info.signatures[i] = new Signature(signatures[i]);
}
shadowOf(packageManager).installPackage(info);
shadowOf(packageManager).setPackagesForUid(uid, packageName);
}
@Test
public void testInternalOnly() throws Exception {
policy = SecurityPolicies.internalOnly();
@ -99,79 +53,4 @@ public final class SecurityPoliciesTest {
assertThat(policy.checkAuthorization(OTHER_UID).getDescription())
.isEqualTo(PERMISSION_DENIED_REASONS);
}
@Test
public void testHasSignatureSha256Hash_succeedsIfPackageNameAndSignatureHashMatch()
throws Exception {
policy =
SecurityPolicies.hasSignatureSha256Hash(
packageManager, OTHER_UID_PACKAGE_NAME, getSha256Hash(SIG2));
// THEN UID for package that has SIG2 will be authorized
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
}
@Test
public void testHasSignatureSha256Hash_failsIfPackageNameDoesNotMatch() throws Exception {
policy =
SecurityPolicies.hasSignatureSha256Hash(
packageManager, appContext.getPackageName(), getSha256Hash(SIG1));
// THEN UID for package that has SIG1 but different package name will not be authorized
assertThat(policy.checkAuthorization(OTHER_UID_SAME_SIGNATURE).getCode())
.isEqualTo(Status.PERMISSION_DENIED.getCode());
}
@Test
public void testHasSignatureSha256Hash_failsIfSignatureHashDoesNotMatch() throws Exception {
policy =
SecurityPolicies.hasSignatureSha256Hash(
packageManager, OTHER_UID_PACKAGE_NAME, getSha256Hash(SIG1));
// THEN UID for package that doesn't have SIG1 will not be authorized
assertThat(policy.checkAuthorization(OTHER_UID).getCode())
.isEqualTo(Status.PERMISSION_DENIED.getCode());
}
@Test
public void testOneOfSignatureSha256Hash_succeedsIfPackageNameAndSignatureHashMatch()
throws Exception {
policy =
SecurityPolicies.oneOfSignatureSha256Hash(
packageManager, OTHER_UID_PACKAGE_NAME, ImmutableList.of(getSha256Hash(SIG2)));
// THEN UID for package that has SIG2 will be authorized
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
}
@Test
public void testOneOfSignatureSha256Hash_failsIfAllHashesDoNotMatch() throws Exception {
policy =
SecurityPolicies.oneOfSignatureSha256Hash(
packageManager,
appContext.getPackageName(),
ImmutableList.of(getSha256Hash(SIG1), getSha256Hash("1314")));
// THEN UID for package that has SIG1 but different package name will not be authorized
assertThat(policy.checkAuthorization(OTHER_UID_SAME_SIGNATURE).getCode())
.isEqualTo(Status.PERMISSION_DENIED.getCode());
}
@Test
public void testOneOfSignatureSha256Hash_succeedsIfPackageNameAndOneOfSignatureHashesMatch()
throws Exception {
policy =
SecurityPolicies.oneOfSignatureSha256Hash(
packageManager,
OTHER_UID_PACKAGE_NAME,
ImmutableList.of(getSha256Hash(SIG1), getSha256Hash(SIG2)));
// THEN UID for package that has SIG2 will be authorized
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
}
private static byte[] getSha256Hash(String signatureString) {
return Hashing.sha256().hashBytes(new Signature(signatureString).toByteArray()).asBytes();
}
}