diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java index 76bc2ab32e..be46b9e3e5 100644 --- a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java +++ b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java @@ -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 requiredSignatureSha256Hashes) { - Preconditions.checkNotNull(packageManager, "packageManager"); - Preconditions.checkNotNull(packageName, "packageName"); - Preconditions.checkNotNull(requiredSignatureSha256Hashes, "requiredSignatureSha256Hashes"); - Preconditions.checkArgument(!requiredSignatureSha256Hashes.isEmpty(), - "requiredSignatureSha256Hashes"); - ImmutableList 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 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 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 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(); - } } diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicy.java b/binder/src/main/java/io/grpc/binder/SecurityPolicy.java index d13f3a863f..d7dad53fdc 100644 --- a/binder/src/main/java/io/grpc/binder/SecurityPolicy.java +++ b/binder/src/main/java/io/grpc/binder/SecurityPolicy.java @@ -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. - * *

IMPORTANT For any concrete extensions of this class, it's assumed that the * authorization status of a given UID will not change as long as a process with that UID is * alive. diff --git a/binder/src/main/java/io/grpc/binder/UntrustedSecurityPolicies.java b/binder/src/main/java/io/grpc/binder/UntrustedSecurityPolicies.java deleted file mode 100644 index 7c842b025a..0000000000 --- a/binder/src/main/java/io/grpc/binder/UntrustedSecurityPolicies.java +++ /dev/null @@ -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; - } - }; - } -} diff --git a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java index b894b5bb4c..6fd9e22eba 100644 --- a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java +++ b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java @@ -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(); - } - }