Merge "Grant signature permission to requesting app with common signer" into sc-dev am: 47c04289f8
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13417592 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: I59c4fa26debf037194d1de041e9cd7bcae9d5ce8
This commit is contained in:
@@ -6264,6 +6264,55 @@ public class PackageParser {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this {@code SigningDetails} has a signer in common with the provided
|
||||
* {@code otherDetails} with the specified {@code flags} capabilities provided by this
|
||||
* signer.
|
||||
*
|
||||
* <p>Note this method allows for the signing lineage to diverge, so this should only be
|
||||
* used for instances where the only requirement is a common signer in the lineage with
|
||||
* the specified capabilities. If the current signer of this instance is an ancestor of
|
||||
* {@code otherDetails} then {@code true} is immediately returned since the current signer
|
||||
* has all capabilities granted.
|
||||
*/
|
||||
public boolean hasCommonSignerWithCapability(SigningDetails otherDetails,
|
||||
@CertCapabilities int flags) {
|
||||
if (this == UNKNOWN || otherDetails == UNKNOWN) {
|
||||
return false;
|
||||
}
|
||||
// If either is signed with more than one signer then both must be signed by the same
|
||||
// signers to consider the capabilities granted.
|
||||
if (signatures.length > 1 || otherDetails.signatures.length > 1) {
|
||||
return signaturesMatchExactly(otherDetails);
|
||||
}
|
||||
// The Signature class does not use the granted capabilities in the hashCode
|
||||
// computation, so a Set can be used to check for a common signer.
|
||||
Set<Signature> otherSignatures = new ArraySet<>();
|
||||
if (otherDetails.hasPastSigningCertificates()) {
|
||||
otherSignatures.addAll(Arrays.asList(otherDetails.pastSigningCertificates));
|
||||
} else {
|
||||
otherSignatures.addAll(Arrays.asList(otherDetails.signatures));
|
||||
}
|
||||
// If the current signer of this instance is an ancestor of the other than return true
|
||||
// since all capabilities are granted to the current signer.
|
||||
if (otherSignatures.contains(signatures[0])) {
|
||||
return true;
|
||||
}
|
||||
if (hasPastSigningCertificates()) {
|
||||
// Since the current signer was checked above and the last signature in the
|
||||
// pastSigningCertificates is the current signer skip checking the last element.
|
||||
for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
|
||||
if (otherSignatures.contains(pastSigningCertificates[i])) {
|
||||
// If the caller specified multiple capabilities ensure all are set.
|
||||
if ((pastSigningCertificates[i].getFlags() & flags) == flags) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided {@code oldDetails} is an ancestor of this one, and whether or
|
||||
* not this one grants it the provided capability, represented by the {@code flags}
|
||||
|
||||
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.pm.PackageParser.SigningDetails;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -35,6 +36,8 @@ import androidx.test.filters.SmallTest;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class SigningDetailsTest {
|
||||
@@ -208,8 +211,8 @@ public class SigningDetailsTest {
|
||||
SigningDetails result1 = noLineageDetails.mergeLineageWith(lineageDetails);
|
||||
SigningDetails result2 = lineageDetails.mergeLineageWith(noLineageDetails);
|
||||
|
||||
assertTrue(result1 == lineageDetails);
|
||||
assertTrue(result2 == lineageDetails);
|
||||
assertSigningDetailsContainsLineage(result1, FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
assertSigningDetailsContainsLineage(result2, FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -271,8 +274,10 @@ public class SigningDetailsTest {
|
||||
SigningDetails result1 = singleSignerDetails.mergeLineageWith(fullLineageDetails);
|
||||
SigningDetails result2 = fullLineageDetails.mergeLineageWith(singleSignerDetails);
|
||||
|
||||
assertTrue(result1 == fullLineageDetails);
|
||||
assertTrue(result2 == fullLineageDetails);
|
||||
assertSigningDetailsContainsLineage(result1, FIRST_SIGNATURE, SECOND_SIGNATURE,
|
||||
THIRD_SIGNATURE);
|
||||
assertSigningDetailsContainsLineage(result2, FIRST_SIGNATURE, SECOND_SIGNATURE,
|
||||
THIRD_SIGNATURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -605,6 +610,213 @@ public class SigningDetailsTest {
|
||||
assertTrue(secondLineageDetails.hasCommonAncestor(firstLineageDetails));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_singleMatchingSigner_returnsTrue()
|
||||
throws Exception {
|
||||
// The hasCommonSignerWithCapabilities method is intended to grant the specified
|
||||
// capabilities to a requesting package that has a common signer in the lineage (or as the
|
||||
// current signer) even if their signing identities have diverged. This test verifies if the
|
||||
// two SigningDetails have the same single signer then the requested capability can be
|
||||
// granted since the current signer always has all capabilities granted.
|
||||
SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE);
|
||||
SigningDetails secondSignerDetails = createSigningDetails(FIRST_SIGNATURE);
|
||||
|
||||
assertTrue(firstDetails.hasCommonSignerWithCapability(secondSignerDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_singleDifferentSigners_returnsFalse()
|
||||
throws Exception {
|
||||
// If each package is signed by a single different signer then the method should return
|
||||
// false since there is no shared signer.
|
||||
SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE);
|
||||
SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE);
|
||||
|
||||
assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
|
||||
assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_oneWithMultipleSigners_returnsFalse()
|
||||
throws Exception {
|
||||
// If one of the packages is signed with multiple signers and the other only a single signer
|
||||
// this method should return false since all signers must match exactly for multiple signer
|
||||
// cases.
|
||||
SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
SigningDetails secondDetails = createSigningDetails(FIRST_SIGNATURE);
|
||||
|
||||
assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
|
||||
assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_multipleMatchingSigners_returnsTrue()
|
||||
throws Exception {
|
||||
// if both packages are signed by the same multiple signers then this method should return
|
||||
// true since the current signer is granted all capabilities.
|
||||
SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
|
||||
SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE, FIRST_SIGNATURE);
|
||||
|
||||
assertTrue(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
|
||||
assertTrue(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_singleSignerInLineage_returnsTrue()
|
||||
throws Exception {
|
||||
// if a single signer is in the lineage and that previous signer has the requested
|
||||
// capability then this method should return true.
|
||||
SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
|
||||
new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
|
||||
new int[]{DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
|
||||
SigningDetails singleSignerDetails = createSigningDetails(FIRST_SIGNATURE);
|
||||
|
||||
assertTrue(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_singleSignerInLineageWOCapability_returnsFalse()
|
||||
throws Exception {
|
||||
// If a single signer is in the lineage and that previous signer does not have the requested
|
||||
// capability then this method should return false.
|
||||
SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
|
||||
new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
|
||||
new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES});
|
||||
SigningDetails singleSignerDetails = createSigningDetails(FIRST_SIGNATURE);
|
||||
|
||||
assertFalse(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_singleSignerMatchesCurrentSigner_returnsTrue()
|
||||
throws Exception {
|
||||
// If a requesting app is signed by the same current signer as an app with a lineage the
|
||||
// method should return true since the current signer is granted all capabilities.
|
||||
SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
|
||||
new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
|
||||
new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES});
|
||||
SigningDetails singleSignerDetails = createSigningDetails(SECOND_SIGNATURE);
|
||||
|
||||
assertTrue(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_divergingSignersWithCommonSigner_returnsTrue()
|
||||
throws Exception {
|
||||
// This method is intended to allow granting a capability to another app that has a common
|
||||
// signer in the lineage with the capability still granted; this test verifies when the
|
||||
// current signers diverge but a common ancestor has the requested capability this method
|
||||
// returns true.
|
||||
SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
|
||||
new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
|
||||
new int[]{DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
|
||||
SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
|
||||
SECOND_SIGNATURE, FOURTH_SIGNATURE);
|
||||
|
||||
assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
|
||||
PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_divergingSignersOneGrantsCapability_returnsTrue()
|
||||
throws Exception {
|
||||
// If apps have multiple common signers in the lineage with one denying the requested
|
||||
// capability but the other granting it this method should return true.
|
||||
SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
|
||||
new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
|
||||
new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
|
||||
SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
|
||||
SECOND_SIGNATURE, FOURTH_SIGNATURE);
|
||||
|
||||
assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
|
||||
PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_divergingSignersNoneGrantCapability_returnsFalse()
|
||||
throws Exception {
|
||||
// If apps have multiple common signers in the lineage with all denying the requested
|
||||
// capability this method should return false.
|
||||
SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
|
||||
new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
|
||||
new int[]{SHARED_USER_ID, AUTH, DEFAULT_CAPABILITIES});
|
||||
SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
|
||||
SECOND_SIGNATURE, FOURTH_SIGNATURE);
|
||||
|
||||
assertFalse(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
|
||||
PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
hasCommonSignerWithCapabilities_divergingSignersNoneGrantsAllCapabilities_returnsTrue()
|
||||
throws Exception {
|
||||
// If an app has multiple common signers in the lineage, each granting one of the requested
|
||||
// capabilities but neither granting all this method should return false since a single
|
||||
// common ancestor must grant all requested capabilities.
|
||||
SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
|
||||
new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
|
||||
new int[]{SHARED_USER_ID, PERMISSION, DEFAULT_CAPABILITIES});
|
||||
SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
|
||||
SECOND_SIGNATURE, FOURTH_SIGNATURE);
|
||||
|
||||
assertFalse(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
|
||||
PERMISSION | SHARED_USER_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_currentSignerInLineageOfRequestingApp_returnsTrue()
|
||||
throws Exception {
|
||||
// If the current signer of an app is in the lineage of the requesting app then this method
|
||||
// should return true since the current signer is granted all capabilities.
|
||||
SigningDetails firstLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
|
||||
SECOND_SIGNATURE);
|
||||
SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
|
||||
SECOND_SIGNATURE, THIRD_SIGNATURE);
|
||||
|
||||
assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
|
||||
PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_currentSignerInLineageOfDeclaringApp_returnsTrue()
|
||||
throws Exception {
|
||||
// If the current signer of a requesting app with a lineage is in the lineage of the
|
||||
// declaring app and that previous signature is granted the requested capability the method
|
||||
// should return true.
|
||||
SigningDetails declaringDetails = createSigningDetailsWithLineageAndCapabilities(
|
||||
new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
|
||||
new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
|
||||
SigningDetails requestingDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
|
||||
SECOND_SIGNATURE);
|
||||
|
||||
assertTrue(declaringDetails.hasCommonSignerWithCapability(requestingDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_oneSignerNullLineage_returns() throws Exception {
|
||||
// While the pastSigningCertificates should only be null in the case of multiple current
|
||||
// signers there are instances where this can be null with a single signer; verify that a
|
||||
// null pastSigningCertificates array in either SigningDetails does not result in a
|
||||
// NullPointerException.
|
||||
SigningDetails firstDetails = createSigningDetails(true, FIRST_SIGNATURE);
|
||||
SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE);
|
||||
|
||||
assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
|
||||
assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasCommonSignerWithCapabilities_unknownSigner_returnsFalse() throws Exception {
|
||||
// An unknown SigningDetails for either instance should immediately result in false being
|
||||
// returned.
|
||||
SigningDetails firstDetails = SigningDetails.UNKNOWN;
|
||||
SigningDetails secondDetails = createSigningDetails(FIRST_SIGNATURE);
|
||||
|
||||
assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
|
||||
assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
|
||||
}
|
||||
|
||||
private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
|
||||
int[] capabilities = new int[signers.length];
|
||||
for (int i = 0; i < capabilities.length; i++) {
|
||||
@@ -629,10 +841,34 @@ public class SigningDetailsTest {
|
||||
}
|
||||
|
||||
private SigningDetails createSigningDetails(String... signers) throws Exception {
|
||||
return createSigningDetails(false, signers);
|
||||
}
|
||||
|
||||
private SigningDetails createSigningDetails(boolean useNullPastSigners, String... signers)
|
||||
throws Exception {
|
||||
Signature[] currentSignatures = new Signature[signers.length];
|
||||
for (int i = 0; i < signers.length; i++) {
|
||||
currentSignatures[i] = new Signature(signers[i]);
|
||||
}
|
||||
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
|
||||
// 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) {
|
||||
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
|
||||
}
|
||||
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
|
||||
}
|
||||
|
||||
private void assertSigningDetailsContainsLineage(SigningDetails details,
|
||||
String... pastSigners) {
|
||||
// This method should only be invoked for results that contain a single signer.
|
||||
assertEquals(1, details.signatures.length);
|
||||
assertTrue(details.signatures[0].toCharsString().equalsIgnoreCase(
|
||||
pastSigners[pastSigners.length - 1]));
|
||||
Set<String> signatures = new ArraySet<>(pastSigners);
|
||||
for (Signature pastSignature : details.pastSigningCertificates) {
|
||||
assertTrue(signatures.remove(pastSignature.toCharsString()));
|
||||
}
|
||||
assertEquals(0, signatures.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3356,11 +3356,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
|
||||
// - or its signing certificate was rotated from the source package's certificate
|
||||
// - or its signing certificate is a previous signing certificate of the defining
|
||||
// package, and the defining package still trusts the old certificate for permissions
|
||||
// - or it shares a common signing certificate in its lineage with the defining package,
|
||||
// and the defining package still trusts the old certificate for permissions
|
||||
// - or it shares the above relationships with the system package
|
||||
final PackageParser.SigningDetails sourceSigningDetails =
|
||||
getSourcePackageSigningDetails(bp);
|
||||
return pkg.getSigningDetails().hasAncestorOrSelf(sourceSigningDetails)
|
||||
|| sourceSigningDetails.checkCapability(
|
||||
return sourceSigningDetails.hasCommonSignerWithCapability(
|
||||
pkg.getSigningDetails(),
|
||||
PackageParser.SigningDetails.CertCapabilities.PERMISSION)
|
||||
|| pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails())
|
||||
|
||||
Reference in New Issue
Block a user