Add support for knownSigner permission protection flag
This commit adds support for a new permission protection flag that allows a permission declaration to also include an array resource of trusted signing certificate digests. A permission declared with this new knownSigner permission flag will be granted to a requesting app if that app's current signer, or any of the previous signers in its lineage, are in the declared trusted signing certificate set. Bug: 172970984 Test: atest SigningDetailsTest Test: atest PkgInstallSignatureVerificationTest Change-Id: I54ff2ac720c3a1828d0606426197f7f0e0d0d4b5
This commit is contained in:
@@ -849,6 +849,7 @@ package android {
|
||||
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
|
||||
field public static final int keycode = 16842949; // 0x10100c5
|
||||
field public static final int killAfterRestore = 16843420; // 0x101029c
|
||||
field public static final int knownCerts = 16844330; // 0x101062a
|
||||
field public static final int label = 16842753; // 0x1010001
|
||||
field public static final int labelFor = 16843718; // 0x10103c6
|
||||
field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
|
||||
|
||||
@@ -2632,6 +2632,7 @@ package android.content.pm {
|
||||
field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000
|
||||
field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
|
||||
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
|
||||
field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
|
||||
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
|
||||
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
|
||||
field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
|
||||
|
||||
@@ -6150,6 +6150,56 @@ public class PackageParser {
|
||||
return getDescendantOrSelf(otherSigningDetails) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instance is currently signed, or has ever been signed, with a
|
||||
* signing certificate from the provided {@link Set} of {@code certDigests}.
|
||||
*
|
||||
* <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
|
||||
* of each trusted certificate with the digest characters in upper case. If this instance
|
||||
* has multiple signers then all signers must be in the provided {@code Set}. If this
|
||||
* instance has a signing lineage then this method will return true if any of the previous
|
||||
* signers in the lineage match one of the entries in the {@code Set}.
|
||||
*/
|
||||
public boolean hasAncestorOrSelfWithDigest(Set<String> certDigests) {
|
||||
if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
// If an app is signed by multiple signers then all of the signers must be in the Set.
|
||||
if (signatures.length > 1) {
|
||||
// If the Set has less elements than the number of signatures then immediately
|
||||
// return false as there's no way to satisfy the requirement of all signatures being
|
||||
// in the Set.
|
||||
if (certDigests.size() < signatures.length) {
|
||||
return false;
|
||||
}
|
||||
for (Signature signature : signatures) {
|
||||
String signatureDigest = PackageUtils.computeSha256Digest(
|
||||
signature.toByteArray());
|
||||
if (!certDigests.contains(signatureDigest)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String signatureDigest = PackageUtils.computeSha256Digest(signatures[0].toByteArray());
|
||||
if (certDigests.contains(signatureDigest)) {
|
||||
return true;
|
||||
}
|
||||
if (hasPastSigningCertificates()) {
|
||||
// The last element in the pastSigningCertificates array is the current signer;
|
||||
// since that was verified above just check all the signers in the lineage.
|
||||
for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
|
||||
signatureDigest = PackageUtils.computeSha256Digest(
|
||||
pastSigningCertificates[i].toByteArray());
|
||||
if (certDigests.contains(signatureDigest)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SigningDetails with a descendant (or same) signer after verifying the
|
||||
* descendant has the same, a superset, or a subset of the lineage of the ancestor.
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.text.TextUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Information you can retrieve about a particular security permission
|
||||
@@ -278,6 +279,15 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
|
||||
@SystemApi
|
||||
public static final int PROTECTION_FLAG_ROLE = 0x4000000;
|
||||
|
||||
/**
|
||||
* Additional flag for {@link #protectionLevel}, correspoinding to the {@code knownSigner} value
|
||||
* of {@link android.R.attr#protectionLevel}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int PROTECTION_FLAG_KNOWN_SIGNER = 0x8000000;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
|
||||
PROTECTION_FLAG_PRIVILEGED,
|
||||
@@ -303,6 +313,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
|
||||
PROTECTION_FLAG_RETAIL_DEMO,
|
||||
PROTECTION_FLAG_RECENTS,
|
||||
PROTECTION_FLAG_ROLE,
|
||||
PROTECTION_FLAG_KNOWN_SIGNER,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ProtectionFlags {}
|
||||
@@ -466,6 +477,15 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
|
||||
*/
|
||||
public @Nullable CharSequence nonLocalizedDescription;
|
||||
|
||||
/**
|
||||
* A {@link Set} of trusted signing certificate digests. If this permission has the {@link
|
||||
* #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
|
||||
* if the app is signed by any of these certificates.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public @Nullable Set<String> knownCerts;
|
||||
|
||||
/** @hide */
|
||||
public static int fixProtectionLevel(int level) {
|
||||
if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
|
||||
@@ -570,6 +590,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
|
||||
if ((level & PermissionInfo.PROTECTION_FLAG_ROLE) != 0) {
|
||||
protLevel.append("|role");
|
||||
}
|
||||
if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
|
||||
protLevel.append("|knownSigner");
|
||||
}
|
||||
return protLevel.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -655,6 +655,7 @@ public class PackageInfoWithoutStateUtils {
|
||||
pi.protectionLevel = p.getProtectionLevel();
|
||||
pi.descriptionRes = p.getDescriptionRes();
|
||||
pi.flags = p.getFlags();
|
||||
pi.knownCerts = p.getKnownCerts();
|
||||
|
||||
if ((flags & PackageManager.GET_META_DATA) == 0) {
|
||||
return pi;
|
||||
|
||||
@@ -26,6 +26,8 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.DataClass;
|
||||
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/** @hide */
|
||||
public class ParsedPermission extends ParsedComponent {
|
||||
|
||||
@@ -39,6 +41,8 @@ public class ParsedPermission extends ParsedComponent {
|
||||
boolean tree;
|
||||
@Nullable
|
||||
private ParsedPermissionGroup parsedPermissionGroup;
|
||||
@Nullable
|
||||
Set<String> knownCerts;
|
||||
|
||||
@VisibleForTesting
|
||||
public ParsedPermission() {
|
||||
@@ -81,6 +85,10 @@ public class ParsedPermission extends ParsedComponent {
|
||||
return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
|
||||
}
|
||||
|
||||
public @Nullable Set<String> getKnownCerts() {
|
||||
return knownCerts;
|
||||
}
|
||||
|
||||
public int calculateFootprint() {
|
||||
int size = getName().length();
|
||||
if (getNonLocalizedLabel() != null) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.pm.parsing.result.ParseResult;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.R;
|
||||
@@ -32,6 +33,8 @@ import com.android.internal.R;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/** @hide */
|
||||
public class ParsedPermissionUtils {
|
||||
@@ -90,6 +93,43 @@ public class ParsedPermissionUtils {
|
||||
permission.flags = sa.getInt(
|
||||
R.styleable.AndroidManifestPermission_permissionFlags, 0);
|
||||
|
||||
final int knownCertsResource = sa.getResourceId(
|
||||
R.styleable.AndroidManifestPermission_knownCerts, 0);
|
||||
if (knownCertsResource != 0) {
|
||||
// The knownCerts attribute supports both a string array resource as well as a
|
||||
// string resource for the case where the permission should only be granted to a
|
||||
// single known signer.
|
||||
final String resourceType = res.getResourceTypeName(knownCertsResource);
|
||||
if (resourceType.equals("array")) {
|
||||
final String[] knownCerts = res.getStringArray(knownCertsResource);
|
||||
if (knownCerts != null) {
|
||||
// Convert the provided digest to upper case for consistent Set membership
|
||||
// checks when verifying the signing certificate digests of requesting apps.
|
||||
permission.knownCerts = new ArraySet<>();
|
||||
for (String knownCert : knownCerts) {
|
||||
permission.knownCerts.add(knownCert.toUpperCase(Locale.US));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final String knownCert = res.getString(knownCertsResource);
|
||||
if (knownCert != null) {
|
||||
permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
|
||||
}
|
||||
}
|
||||
if (permission.knownCerts == null) {
|
||||
Slog.w(TAG, packageName + " defines a knownSigner permission but"
|
||||
+ " the provided knownCerts resource is null");
|
||||
}
|
||||
} else {
|
||||
// If the knownCerts resource ID is null check if the app specified a string
|
||||
// value for the attribute representing a single trusted signer.
|
||||
final String knownCert = sa.getString(
|
||||
R.styleable.AndroidManifestPermission_knownCerts);
|
||||
if (knownCert != null) {
|
||||
permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
|
||||
}
|
||||
}
|
||||
|
||||
// For now only platform runtime permissions can be restricted
|
||||
if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
|
||||
permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
|
||||
|
||||
@@ -311,6 +311,10 @@
|
||||
<flag name="recents" value="0x2000000" />
|
||||
<!-- Additional flag from base permission type: this permission is managed by role. -->
|
||||
<flag name="role" value="0x4000000" />
|
||||
<!-- Additional flag from base permission type: this permission can also be granted if the
|
||||
requesting application is signed by, or has in its signing lineage, any of the
|
||||
certificate digests declared in {@link android.R.attr#knownCerts}. -->
|
||||
<flag name="knownSigner" value="0x8000000" />
|
||||
</attr>
|
||||
|
||||
<!-- Flags indicating more context for a permission group. -->
|
||||
@@ -364,6 +368,15 @@
|
||||
{@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->
|
||||
<attr name="permissionGroup" format="string" />
|
||||
|
||||
<!-- A reference to an array resource containing the signing certificate digests to be granted
|
||||
this permission when using the {@code knownSigner} protection flag. The digest should
|
||||
be computed over the DER encoding of the trusted certificate using the SHA-256 digest
|
||||
algorithm.
|
||||
<p>
|
||||
If only a single signer is declared this can also be a string resource, or the digest
|
||||
can be declared inline as the value for this attribute. -->
|
||||
<attr name="knownCerts" format="reference|string" />
|
||||
|
||||
<!-- Specify the name of a user ID that will be shared between multiple
|
||||
packages. By default, each package gets its own unique user-id.
|
||||
By setting this value on two or more packages, each of these packages
|
||||
@@ -1935,6 +1948,7 @@
|
||||
<attr name="request" />
|
||||
<attr name="protectionLevel" />
|
||||
<attr name="permissionFlags" />
|
||||
<attr name="knownCerts" />
|
||||
</declare-styleable>
|
||||
|
||||
<!-- The <code>permission-group</code> tag declares a logical grouping of
|
||||
|
||||
@@ -3064,6 +3064,7 @@
|
||||
<public name="previewLayout" />
|
||||
<public name="clipToOutline" />
|
||||
<public name="edgeEffectType" />
|
||||
<public name="knownCerts" />
|
||||
</public-group>
|
||||
|
||||
<public-group type="drawable" first-id="0x010800b5">
|
||||
|
||||
@@ -29,6 +29,7 @@ import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.pm.PackageParser.SigningDetails;
|
||||
import android.util.ArraySet;
|
||||
import android.util.PackageUtils;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -817,6 +818,124 @@ public class SigningDetailsTest {
|
||||
assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_nullSet_returnsFalse() throws Exception {
|
||||
// The hasAncestorOrSelfWithDigest method is intended to verify whether the SigningDetails
|
||||
// is currently signed, or has previously been signed, by any of the certificate digests
|
||||
// in the provided Set. This test verifies if a null Set is provided then false is returned.
|
||||
SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
|
||||
|
||||
assertFalse(details.hasAncestorOrSelfWithDigest(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_unknownDetails_returnsFalse() throws Exception {
|
||||
// If hasAncestorOrSelfWithDigest is invoked against an UNKNOWN
|
||||
// instance of the SigningDetails then false is returned.
|
||||
SigningDetails details = SigningDetails.UNKNOWN;
|
||||
Set<String> digests = createDigestSet(FIRST_SIGNATURE);
|
||||
|
||||
assertFalse(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_singleSignerInSet_returnsTrue() throws Exception {
|
||||
// If the single signer of an app is in the provided digest Set then
|
||||
// the method should return true.
|
||||
SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
|
||||
assertTrue(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_singleSignerNotInSet_returnsFalse() throws Exception {
|
||||
// If the single signer of an app is not in the provided digest Set then
|
||||
// the method should return false.
|
||||
SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
|
||||
|
||||
assertFalse(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_multipleSignersInSet_returnsTrue() throws Exception {
|
||||
// If an app is signed by multiple signers and all of the signers are in
|
||||
// the digest Set then the method should return true.
|
||||
SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE);
|
||||
|
||||
assertTrue(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_multipleSignersNotInSet_returnsFalse()
|
||||
throws Exception {
|
||||
// If an app is signed by multiple signers then all signers must be in the digest Set; if
|
||||
// only a subset of the signers are in the Set then the method should return false.
|
||||
SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
|
||||
|
||||
assertFalse(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_multipleSignersOneInSet_returnsFalse()
|
||||
throws Exception {
|
||||
// If an app is signed by multiple signers and the Set size is smaller than the number of
|
||||
// signers then the method should immediately return false since there's no way for the
|
||||
// requirement of all signers in the Set to be met.
|
||||
SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(FIRST_SIGNATURE);
|
||||
|
||||
assertFalse(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_lineageSignerInSet_returnsTrue() throws Exception {
|
||||
// If an app has a rotated signing key and a previous key in the lineage is in the digest
|
||||
// Set then this method should return true.
|
||||
SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
|
||||
|
||||
assertTrue(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_lineageSignerNotInSet_returnsFalse() throws Exception {
|
||||
// If an app has a rotated signing key, but neither the current key nor any of the signers
|
||||
// in the lineage are in the digest set then the method should return false.
|
||||
SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(THIRD_SIGNATURE, FOURTH_SIGNATURE);
|
||||
|
||||
assertFalse(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_lastSignerInLineageInSet_returnsTrue()
|
||||
throws Exception {
|
||||
// If an app has multiple signers in the lineage only one of those signers must be in the
|
||||
// Set for this method to return true. This test verifies if the last signer in the lineage
|
||||
// is in the set then the method returns true.
|
||||
SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE,
|
||||
THIRD_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(SECOND_SIGNATURE);
|
||||
|
||||
assertTrue(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAncestorOrSelfWithDigest_nullLineageSingleSIgner_returnsFalse()
|
||||
throws Exception {
|
||||
// Under some instances an app with only a single signer can have a null lineage; this
|
||||
// test verifies that null lineage does not result in a NullPointerException and instead the
|
||||
// method returns false if the single signer is not in the Set.
|
||||
SigningDetails details = createSigningDetails(true, FIRST_SIGNATURE);
|
||||
Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
|
||||
|
||||
assertFalse(details.hasAncestorOrSelfWithDigest(digests));
|
||||
}
|
||||
|
||||
private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
|
||||
int[] capabilities = new int[signers.length];
|
||||
for (int i = 0; i < capabilities.length; i++) {
|
||||
@@ -853,12 +972,21 @@ public class SigningDetailsTest {
|
||||
// If there are multiple signers then the pastSigningCertificates should be set to null, but
|
||||
// if there is only a single signer both the current signer and the past signers should be
|
||||
// set to that one signer.
|
||||
if (signers.length > 1) {
|
||||
if (signers.length > 1 || useNullPastSigners) {
|
||||
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
|
||||
}
|
||||
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
|
||||
}
|
||||
|
||||
private Set<String> createDigestSet(String... signers) {
|
||||
Set<String> digests = new ArraySet<>();
|
||||
for (String signer : signers) {
|
||||
String digest = PackageUtils.computeSha256Digest(new Signature(signer).toByteArray());
|
||||
digests.add(digest);
|
||||
}
|
||||
return digests;
|
||||
}
|
||||
|
||||
private void assertSigningDetailsContainsLineage(SigningDetails details,
|
||||
String... pastSigners) {
|
||||
// This method should only be invoked for results that contain a single signer.
|
||||
|
||||
@@ -38,6 +38,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Permission definition.
|
||||
@@ -345,6 +346,14 @@ public final class Permission {
|
||||
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0;
|
||||
}
|
||||
|
||||
public boolean isKnownSigner() {
|
||||
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0;
|
||||
}
|
||||
|
||||
public Set<String> getKnownCerts() {
|
||||
return mPermissionInfo.knownCerts;
|
||||
}
|
||||
|
||||
public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) {
|
||||
if (!oldPackageName.equals(mPermissionInfo.packageName)) {
|
||||
return;
|
||||
|
||||
@@ -3438,6 +3438,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
|
||||
// Any pre-installed system app is allowed to get this permission.
|
||||
allowed = true;
|
||||
}
|
||||
if (!allowed && bp.isKnownSigner()) {
|
||||
// If the permission is to be granted to a known signer then check if any of this
|
||||
// app's signing certificates are in the trusted certificate digest Set.
|
||||
allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts());
|
||||
}
|
||||
// Deferred to be checked under permission data lock inside restorePermissionState().
|
||||
//if (!allowed && bp.isDevelopment()) {
|
||||
// // For development permissions, a development permission
|
||||
|
||||
Reference in New Issue
Block a user