mirror of https://github.com/grpc/grpc-java.git
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:
parent
997592192b
commit
ee395e0e43
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue