diff --git a/core/api/current.txt b/core/api/current.txt
index 53be53c62786a..4152a47514e93 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -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
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index dffa0cc315ae6..632c1d4fec001 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -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
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0819d1743ad6b..bf8d1f6ab07b1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -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}.
+ *
+ *
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 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.
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 35f02a8d5dd25..a2e533af64a0c 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -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 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();
}
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index fb0d904905677..9a84ded99c679 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -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;
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index f99a0b1dcadb8..35bb33c84d567 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -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 knownCerts;
@VisibleForTesting
public ParsedPermission() {
@@ -81,6 +85,10 @@ public class ParsedPermission extends ParsedComponent {
return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
}
+ public @Nullable Set getKnownCerts() {
+ return knownCerts;
+ }
+
public int calculateFootprint() {
int size = getName().length();
if (getNonLocalizedLabel() != null) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index 9012b5ce2b1e5..a7cecbee8aec1 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -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;
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b4e580aac959c..0ae6a76e2a606 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -311,6 +311,10 @@
+
+
@@ -364,6 +368,15 @@
{@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->
+
+
+