blob: bb09ad23f6ec662172792d3d7e6eeed0384b0469 [file]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* 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 android.content.pm;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.PackageUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import libcore.util.HexEncoding;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* A container for signing-related data of an application package.
*
* @hide
*/
@DataClass(genConstructor = false, genConstDefs = false, genParcelable = true, genAidl = false)
public final class SigningDetails implements Parcelable {
private static final String TAG = "SigningDetails";
@Retention(RetentionPolicy.SOURCE)
@IntDef({SignatureSchemeVersion.UNKNOWN,
SignatureSchemeVersion.JAR,
SignatureSchemeVersion.SIGNING_BLOCK_V2,
SignatureSchemeVersion.SIGNING_BLOCK_V3,
SignatureSchemeVersion.SIGNING_BLOCK_V4})
public @interface SignatureSchemeVersion {
int UNKNOWN = 0;
int JAR = 1;
int SIGNING_BLOCK_V2 = 2;
int SIGNING_BLOCK_V3 = 3;
int SIGNING_BLOCK_V4 = 4;
}
/** The signing certificates associated with this application package. */
private final @Nullable Signature[] mSignatures;
/** The signature scheme version for this application package. */
private final @SignatureSchemeVersion int mSignatureSchemeVersion;
/** The public keys set for the certificates. */
private final @Nullable ArraySet<PublicKey> mPublicKeys;
/**
* APK Signature Scheme v3 includes support for adding a proof-of-rotation record that
* contains two pieces of information:
* 1) the past signing certificates
* 2) the flags that APK wants to assign to each of the past signing certificates.
*
* This collection of {@code Signature} objects, each of which is formed from a former
* signing certificate of this APK before it was changed by signing certificate rotation,
* represents the first piece of information. It is the APK saying to the rest of the
* world: "hey if you trust the old cert, you can trust me!" This is useful, if for
* instance, the platform would like to determine whether or not to allow this APK to do
* something it would've allowed it to do under the old cert (like upgrade).
*/
private final @Nullable Signature[] mPastSigningCertificates;
/** special value used to see if cert is in package - not exposed to callers */
private static final int PAST_CERT_EXISTS = 0;
@IntDef(flag = true,
value = {CertCapabilities.INSTALLED_DATA,
CertCapabilities.SHARED_USER_ID,
CertCapabilities.PERMISSION,
CertCapabilities.ROLLBACK})
public @interface CertCapabilities {
/** accept data from already installed pkg with this cert */
int INSTALLED_DATA = 1;
/** accept sharedUserId with pkg with this cert */
int SHARED_USER_ID = 2;
/** grant SIGNATURE permissions to pkgs with this cert */
int PERMISSION = 4;
/** allow pkg to update to one signed by this certificate */
int ROLLBACK = 8;
/** allow pkg to continue to have auth access gated by this cert */
int AUTH = 16;
}
@IntDef(value = {CapabilityMergeRule.MERGE_SELF_CAPABILITY,
CapabilityMergeRule.MERGE_OTHER_CAPABILITY,
CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY})
public @interface CapabilityMergeRule {
/**
* When capabilities are different for a common signer in the lineage, use the capabilities
* from this instance.
*/
int MERGE_SELF_CAPABILITY = 0;
/**
* When capabilities are different for a common signer in the lineage, use the capabilites
* from the other instance.
*/
int MERGE_OTHER_CAPABILITY = 1;
/**
* When capabilities are different for a common signer in the lineage, use the most
* restrictive between the two signers.
*/
int MERGE_RESTRICTED_CAPABILITY = 2;
}
/** A representation of unknown signing details. Use instead of null. */
public static final SigningDetails UNKNOWN = new SigningDetails(/* signatures */ null,
SignatureSchemeVersion.UNKNOWN, /* keys */ null, /* pastSigningCertificates */ null);
@VisibleForTesting
public SigningDetails(@Nullable Signature[] signatures,
@SignatureSchemeVersion int signatureSchemeVersion,
@Nullable ArraySet<PublicKey> keys, @Nullable Signature[] pastSigningCertificates) {
mSignatures = signatures;
mSignatureSchemeVersion = signatureSchemeVersion;
mPublicKeys = keys;
mPastSigningCertificates = pastSigningCertificates;
}
public SigningDetails(@Nullable Signature[] signatures,
@SignatureSchemeVersion int signatureSchemeVersion,
@Nullable Signature[] pastSigningCertificates)
throws CertificateException {
this(signatures, signatureSchemeVersion, toSigningKeys(signatures),
pastSigningCertificates);
}
public SigningDetails(@Nullable Signature[] signatures,
@SignatureSchemeVersion int signatureSchemeVersion)
throws CertificateException {
this(signatures, signatureSchemeVersion, /* pastSigningCertificates */ null);
}
public SigningDetails(@Nullable SigningDetails orig) {
if (orig != null) {
if (orig.mSignatures != null) {
mSignatures = orig.mSignatures.clone();
} else {
mSignatures = null;
}
mSignatureSchemeVersion = orig.mSignatureSchemeVersion;
mPublicKeys = new ArraySet<>(orig.mPublicKeys);
if (orig.mPastSigningCertificates != null) {
mPastSigningCertificates = orig.mPastSigningCertificates.clone();
} else {
mPastSigningCertificates = null;
}
} else {
mSignatures = null;
mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
mPublicKeys = null;
mPastSigningCertificates = null;
}
}
/**
* Merges the signing lineage of this instance with the lineage in the provided {@code
* otherSigningDetails} using {@link CapabilityMergeRule#MERGE_OTHER_CAPABILITY} as the merge
* rule.
*
* @param otherSigningDetails the {@code SigningDetails} with which to merge
* @return Merged {@code SigningDetails} instance when one has the same or an ancestor signer
* of the other. If neither instance has a lineage, or if neither has the same or an
* ancestor signer then this instance is returned.
* @see #mergeLineageWith(SigningDetails, int)
*/
public @NonNull SigningDetails mergeLineageWith(@NonNull SigningDetails otherSigningDetails) {
return mergeLineageWith(otherSigningDetails, CapabilityMergeRule.MERGE_OTHER_CAPABILITY);
}
/**
* Merges the signing lineage of this instance with the lineage in the provided {@code
* otherSigningDetails} when one has the same or an ancestor signer of the other using the
* provided {@code mergeRule} to handle differences in capabilities for shared signers.
*
* <p>Merging two signing lineages will result in a new {@code SigningDetails} instance
* containing the longest common lineage with differences in capabilities for shared signers
* resolved using the provided {@code mergeRule}. If the two lineages contain the same signers
* with the same capabilities then the instance on which this was invoked is returned without
* any changes. Similarly if neither instance has a lineage, or if neither has the same or an
* ancestor signer then this instance is returned.
*
* Following are some example results of this method for lineages with signers A, B, C, D:
* <ul>
* <li>lineage B merged with lineage A -> B returns lineage A -> B.
* <li>lineage A -> B merged with lineage B -> C returns lineage A -> B -> C
* <li>lineage A -> B with the {@code PERMISSION} capability revoked for A merged with
* lineage A -> B with the {@code SHARED_USER_ID} capability revoked for A returns the
* following based on the {@code mergeRule}:
* <ul>
* <li>{@code MERGE_SELF_CAPABILITY} - lineage A -> B with {@code PERMISSION} revoked
* for A.
* <li>{@code MERGE_OTHER_CAPABILITY} - lineage A -> B with {@code SHARED_USER_ID}
* revoked for A.
* <li>{@code MERGE_RESTRICTED_CAPABILITY} - lineage A -> B with {@code PERMISSION} and
* {@code SHARED_USER_ID} revoked for A.
* </ul>
* <li>lineage A -> B -> C merged with lineage A -> B -> D would return the original lineage
* A -> B -> C since the current signer of both instances is not the same or in the
* lineage of the other.
* </ul>
*
* @param otherSigningDetails the {@code SigningDetails} with which to merge
* @param mergeRule the {@link CapabilityMergeRule} to use when resolving differences in
* capabilities for shared signers
* @return Merged {@code SigningDetails} instance when one has the same or an ancestor signer
* of the other. If neither instance has a lineage, or if neither has the same or an
* ancestor signer then this instance is returned.
*/
public @NonNull SigningDetails mergeLineageWith(@NonNull SigningDetails otherSigningDetails,
@CapabilityMergeRule int mergeRule) {
if (!hasPastSigningCertificates()) {
return otherSigningDetails.hasPastSigningCertificates()
&& otherSigningDetails.hasAncestorOrSelf(this) ? otherSigningDetails : this;
}
if (!otherSigningDetails.hasPastSigningCertificates()) {
return this;
}
// Use the utility method to determine which SigningDetails instance is the descendant
// and to confirm that the signing lineage does not diverge.
SigningDetails descendantSigningDetails = getDescendantOrSelf(otherSigningDetails);
if (descendantSigningDetails == null) {
return this;
}
SigningDetails mergedDetails = this;
if (descendantSigningDetails == this) {
// If this instance is the descendant then the merge will also be invoked against this
// instance and the provided mergeRule can be used as is.
mergedDetails = mergeLineageWithAncestorOrSelf(otherSigningDetails, mergeRule);
} else {
// If the provided instance is the descendant then the merge will be invoked against the
// other instance and a self or other merge rule will need to be flipped.
switch (mergeRule) {
case CapabilityMergeRule.MERGE_SELF_CAPABILITY:
mergedDetails = otherSigningDetails.mergeLineageWithAncestorOrSelf(this,
CapabilityMergeRule.MERGE_OTHER_CAPABILITY);
break;
case CapabilityMergeRule.MERGE_OTHER_CAPABILITY:
mergedDetails = otherSigningDetails.mergeLineageWithAncestorOrSelf(this,
CapabilityMergeRule.MERGE_SELF_CAPABILITY);
break;
case CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY:
mergedDetails = otherSigningDetails.mergeLineageWithAncestorOrSelf(this,
mergeRule);
break;
}
}
return mergedDetails;
}
/**
* Merges the signing lineage of this instance with the lineage of the ancestor (or same)
* signer in the provided {@code otherSigningDetails}.
*
* @param otherSigningDetails the {@code SigningDetails} with which to merge
* @param mergeRule the {@link CapabilityMergeRule} to use when resolving differences in
* capabilities for shared signers
* @return Merged {@code SigningDetails} instance.
*/
private @NonNull SigningDetails mergeLineageWithAncestorOrSelf(
@NonNull SigningDetails otherSigningDetails, @CapabilityMergeRule int mergeRule) {
// This method should only be called with instances that contain lineages.
int index = mPastSigningCertificates.length - 1;
int otherIndex = otherSigningDetails.mPastSigningCertificates.length - 1;
if (index < 0 || otherIndex < 0) {
return this;
}
List<Signature> mergedSignatures = new ArrayList<>();
boolean capabilitiesModified = false;
// If this is a descendant lineage then add all of the descendant signer(s) to the
// merged lineage until the ancestor signer is reached.
while (index >= 0 && !mPastSigningCertificates[index].equals(
otherSigningDetails.mPastSigningCertificates[otherIndex])) {
mergedSignatures.add(new Signature(mPastSigningCertificates[index--]));
}
// If the signing lineage was exhausted then the provided ancestor is not actually an
// ancestor of this lineage.
if (index < 0) {
return this;
}
do {
// Add the common signer to the merged lineage and resolve any differences in
// capabilites with the merge rule.
Signature signature = mPastSigningCertificates[index--];
Signature ancestorSignature =
otherSigningDetails.mPastSigningCertificates[otherIndex--];
Signature mergedSignature = new Signature(signature);
if (signature.getFlags() != ancestorSignature.getFlags()) {
capabilitiesModified = true;
switch (mergeRule) {
case CapabilityMergeRule.MERGE_SELF_CAPABILITY:
mergedSignature.setFlags(signature.getFlags());
break;
case CapabilityMergeRule.MERGE_OTHER_CAPABILITY:
mergedSignature.setFlags(ancestorSignature.getFlags());
break;
case CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY:
mergedSignature.setFlags(
signature.getFlags() & ancestorSignature.getFlags());
break;
}
}
mergedSignatures.add(mergedSignature);
} while (index >= 0 && otherIndex >= 0 && mPastSigningCertificates[index].equals(
otherSigningDetails.mPastSigningCertificates[otherIndex]));
// If both lineages still have elements then their lineages have diverged; since this is
// not supported return the invoking instance.
if (index >= 0 && otherIndex >= 0) {
return this;
}
// Add any remaining elements from either lineage that is not yet exhausted to the
// the merged lineage.
while (otherIndex >= 0) {
mergedSignatures.add(new Signature(
otherSigningDetails.mPastSigningCertificates[otherIndex--]));
}
while (index >= 0) {
mergedSignatures.add(new Signature(mPastSigningCertificates[index--]));
}
// if this lineage already contains all the elements in the ancestor and none of the
// capabilities were changed then just return this instance.
if (mergedSignatures.size() == mPastSigningCertificates.length
&& !capabilitiesModified) {
return this;
}
// Since the signatures were added to the merged lineage from newest to oldest reverse
// the list to ensure the oldest signer is at index 0.
Collections.reverse(mergedSignatures);
try {
return new SigningDetails(new Signature[]{new Signature(mSignatures[0])},
mSignatureSchemeVersion, mergedSignatures.toArray(new Signature[0]));
} catch (CertificateException e) {
Slog.e(TAG, "Caught an exception creating the merged lineage: ", e);
return this;
}
}
/**
* Returns whether this and the provided {@code otherSigningDetails} share a common
* ancestor.
*
* <p>The two SigningDetails have a common ancestor if any of the following conditions are
* met:
* - If neither has a lineage and their current signer(s) are equal.
* - If only one has a lineage and the signer of the other is the same or in the lineage.
* - If both have a lineage and their current signers are the same or one is in the lineage
* of the other, and their lineages do not diverge to different signers.
*/
public boolean hasCommonAncestor(@NonNull SigningDetails otherSigningDetails) {
if (!hasPastSigningCertificates()) {
// If this instance does not have a lineage then it must either be in the ancestry
// of or the same signer of the otherSigningDetails.
return otherSigningDetails.hasAncestorOrSelf(this);
}
if (!otherSigningDetails.hasPastSigningCertificates()) {
return hasAncestorOrSelf(otherSigningDetails);
}
// If both have a lineage then use getDescendantOrSelf to obtain the descendant signing
// details; a null return from that method indicates there is no common lineage between
// the two or that they diverge at a point in the lineage.
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(@Nullable 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 (mSignatures.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() < mSignatures.length) {
return false;
}
for (Signature signature : mSignatures) {
String signatureDigest = PackageUtils.computeSha256Digest(
signature.toByteArray());
if (!certDigests.contains(signatureDigest)) {
return false;
}
}
return true;
}
String signatureDigest = PackageUtils.computeSha256Digest(mSignatures[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 < mPastSigningCertificates.length - 1; i++) {
signatureDigest = PackageUtils.computeSha256Digest(
mPastSigningCertificates[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.
*
* <p>If this instance and the provided {@code otherSigningDetails} do not share an
* ancestry, or if their lineages diverge then null is returned to indicate there is no
* valid descendant SigningDetails.
*/
private @Nullable SigningDetails getDescendantOrSelf(
@NonNull SigningDetails otherSigningDetails) {
final SigningDetails descendantSigningDetails;
final SigningDetails ancestorSigningDetails;
if (hasAncestorOrSelf(otherSigningDetails)) {
// If the otherSigningDetails has the same signer or a signer in the lineage of this
// instance then treat this instance as the descendant.
descendantSigningDetails = this;
ancestorSigningDetails = otherSigningDetails;
} else if (otherSigningDetails.hasAncestor(this)) {
// The above check confirmed that the two instances do not have the same signer and
// the signer of otherSigningDetails is not in this instance's lineage; if this
// signer is in the otherSigningDetails lineage then treat this as the ancestor.
descendantSigningDetails = otherSigningDetails;
ancestorSigningDetails = this;
} else {
// The signers are not the same and neither has the current signer of the other in
// its lineage; return null to indicate there is no descendant signer.
return null;
}
// Once the descent (or same) signer is identified iterate through the ancestry until
// the current signer of the ancestor is found.
int descendantIndex = descendantSigningDetails.mPastSigningCertificates.length - 1;
int ancestorIndex = ancestorSigningDetails.mPastSigningCertificates.length - 1;
while (descendantIndex >= 0
&& !descendantSigningDetails.mPastSigningCertificates[descendantIndex].equals(
ancestorSigningDetails.mPastSigningCertificates[ancestorIndex])) {
descendantIndex--;
}
// Since the ancestry was verified above the descendant lineage should never be
// exhausted, but if for some reason the ancestor signer is not found then return null.
if (descendantIndex < 0) {
return null;
}
// Once the common ancestor (or same) signer is found iterate over the lineage of both
// to ensure that they are either the same or one is a subset of the other.
do {
descendantIndex--;
ancestorIndex--;
} while (descendantIndex >= 0 && ancestorIndex >= 0
&& descendantSigningDetails.mPastSigningCertificates[descendantIndex].equals(
ancestorSigningDetails.mPastSigningCertificates[ancestorIndex]));
// If both lineages still have elements then they diverge and cannot be considered a
// valid common lineage.
if (descendantIndex >= 0 && ancestorIndex >= 0) {
return null;
}
// Since one or both of the lineages was exhausted they are either the same or one is a
// subset of the other; return the valid descendant.
return descendantSigningDetails;
}
/** Returns true if the signing details have one or more signatures. */
public boolean hasSignatures() {
return mSignatures != null && mSignatures.length > 0;
}
/** Returns true if the signing details have past signing certificates. */
public boolean hasPastSigningCertificates() {
return mPastSigningCertificates != null && mPastSigningCertificates.length > 0;
}
/**
* Determines if the provided {@code oldDetails} is an ancestor of or the same as this one.
* If the {@code oldDetails} signing certificate appears in our pastSigningCertificates,
* then that means it has authorized a signing certificate rotation, which eventually leads
* to our certificate, and thus can be trusted. If this method evaluates to true, this
* SigningDetails object should be trusted if the previous one is.
*/
public boolean hasAncestorOrSelf(@NonNull SigningDetails oldDetails) {
if (this == UNKNOWN || oldDetails == UNKNOWN) {
return false;
}
if (oldDetails.mSignatures.length > 1) {
// multiple-signer packages cannot rotate signing certs, so we just compare current
// signers for an exact match
return signaturesMatchExactly(oldDetails);
} else {
// we may have signing certificate rotation history, check to see if the oldDetails
// was one of our old signing certificates
return hasCertificate(oldDetails.mSignatures[0]);
}
}
/**
* Similar to {@code hasAncestorOrSelf}. Returns true only if this {@code SigningDetails}
* is a descendant of {@code oldDetails}, not if they're the same. This is used to
* determine if this object is newer than the provided one.
*/
public boolean hasAncestor(@NonNull SigningDetails oldDetails) {
if (this == UNKNOWN || oldDetails == UNKNOWN) {
return false;
}
if (hasPastSigningCertificates() && oldDetails.mSignatures.length == 1) {
// the last entry in pastSigningCertificates is the current signer, ignore it
for (int i = 0; i < mPastSigningCertificates.length - 1; i++) {
if (mPastSigningCertificates[i].equals(oldDetails.mSignatures[0])) {
return true;
}
}
}
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(@NonNull 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 (mSignatures.length > 1 || otherDetails.mSignatures.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.mPastSigningCertificates));
} else {
otherSignatures.addAll(Arrays.asList(otherDetails.mSignatures));
}
// 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(mSignatures[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 < mPastSigningCertificates.length - 1; i++) {
if (otherSignatures.contains(mPastSigningCertificates[i])) {
// If the caller specified multiple capabilities ensure all are set.
if ((mPastSigningCertificates[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}
* parameter. In the event of signing certificate rotation, a package may still interact
* with entities signed by its old signing certificate and not want to break previously
* functioning behavior. The {@code flags} value determines which capabilities the app
* signed by the newer signing certificate would like to continue to give to its previous
* signing certificate(s).
*/
public boolean checkCapability(@NonNull SigningDetails oldDetails,
@CertCapabilities int flags) {
if (this == UNKNOWN || oldDetails == UNKNOWN) {
return false;
}
if (oldDetails.mSignatures.length > 1) {
// multiple-signer packages cannot rotate signing certs, so we must have an exact
// match, which also means all capabilities are granted
return signaturesMatchExactly(oldDetails);
} else {
// we may have signing certificate rotation history, check to see if the oldDetails
// was one of our old signing certificates, and if we grant it the capability it's
// requesting
return hasCertificate(oldDetails.mSignatures[0], flags);
}
}
/**
* A special case of {@code checkCapability} which re-encodes both sets of signing
* certificates to counteract a previous re-encoding.
*/
public boolean checkCapabilityRecover(@NonNull SigningDetails oldDetails,
@CertCapabilities int flags) throws CertificateException {
if (oldDetails == UNKNOWN || this == UNKNOWN) {
return false;
}
if (hasPastSigningCertificates() && oldDetails.mSignatures.length == 1) {
// signing certificates may have rotated, check entire history for effective match
for (int i = 0; i < mPastSigningCertificates.length; i++) {
if (Signature.areEffectiveMatch(
oldDetails.mSignatures[0],
mPastSigningCertificates[i])
&& mPastSigningCertificates[i].getFlags() == flags) {
return true;
}
}
} else {
return Signature.areEffectiveMatch(oldDetails, this);
}
return false;
}
/**
* Determine if {@code signature} is in this SigningDetails' signing certificate history,
* including the current signer. Automatically returns false if this object has multiple
* signing certificates, since rotation is only supported for single-signers; this is
* enforced by {@code hasCertificateInternal}.
*/
public boolean hasCertificate(@NonNull Signature signature) {
return hasCertificateInternal(signature, PAST_CERT_EXISTS);
}
/**
* Determine if {@code signature} is in this SigningDetails' signing certificate history,
* including the current signer, and whether or not it has the given permission.
* Certificates which match our current signer automatically get all capabilities.
* Automatically returns false if this object has multiple signing certificates, since
* rotation is only supported for single-signers.
*/
public boolean hasCertificate(@NonNull Signature signature, @CertCapabilities int flags) {
return hasCertificateInternal(signature, flags);
}
/** Convenient wrapper for calling {@code hasCertificate} with certificate's raw bytes. */
public boolean hasCertificate(byte[] certificate) {
Signature signature = new Signature(certificate);
return hasCertificate(signature);
}
private boolean hasCertificateInternal(@NonNull Signature signature, int flags) {
if (this == UNKNOWN) {
return false;
}
// only single-signed apps can have pastSigningCertificates
if (hasPastSigningCertificates()) {
// check all past certs, except for the current one, which automatically gets all
// capabilities, since it is the same as the current signature
for (int i = 0; i < mPastSigningCertificates.length - 1; i++) {
if (mPastSigningCertificates[i].equals(signature)) {
if (flags == PAST_CERT_EXISTS
|| (flags & mPastSigningCertificates[i].getFlags()) == flags) {
return true;
}
}
}
}
// not in previous certs signing history, just check the current signer and make sure
// we are singly-signed
return mSignatures.length == 1 && mSignatures[0].equals(signature);
}
/**
* Determines if the provided {@code sha256String} is an ancestor of this one, and whether
* or not this one grants it the provided capability, represented by the {@code flags}
* parameter. In the event of signing certificate rotation, a package may still interact
* with entities signed by its old signing certificate and not want to break previously
* functioning behavior. The {@code flags} value determines which capabilities the app
* signed by the newer signing certificate would like to continue to give to its previous
* signing certificate(s).
*
* @param sha256String A hex-encoded representation of a sha256 digest. In the case of an
* app with multiple signers, this represents the hex-encoded sha256
* digest of the combined hex-encoded sha256 digests of each individual
* signing certificate according to {@link
* PackageUtils#computeSignaturesSha256Digest(Signature[])}
*/
public boolean checkCapability(@Nullable String sha256String, @CertCapabilities int flags) {
if (this == UNKNOWN || TextUtils.isEmpty(sha256String)) {
return false;
}
// first see if the hash represents a single-signer in our signing history
final byte[] sha256Bytes = HexEncoding.decode(sha256String, false /* allowSingleChar */);
if (hasSha256Certificate(sha256Bytes, flags)) {
return true;
}
// Not in signing history, either represents multiple signatures or not a match.
// Multiple signers can't rotate, so no need to check flags, just see if the SHAs match.
// We already check the single-signer case above as part of hasSha256Certificate, so no
// need to verify we have multiple signers, just run the old check
// just consider current signing certs
final String[] mSignaturesSha256Digests =
PackageUtils.computeSignaturesSha256Digests(mSignatures);
final String mSignaturesSha256Digest =
PackageUtils.computeSignaturesSha256Digest(mSignaturesSha256Digests);
return mSignaturesSha256Digest.equals(sha256String);
}
/**
* Determine if the {@code sha256Certificate} is in this SigningDetails' signing certificate
* history, including the current signer. Automatically returns false if this object has
* multiple signing certificates, since rotation is only supported for single-signers.
*/
public boolean hasSha256Certificate(byte[] sha256Certificate) {
return hasSha256CertificateInternal(sha256Certificate, PAST_CERT_EXISTS);
}
/**
* Determine if the {@code sha256Certificate} certificate hash corresponds to a signing
* certificate in this SigningDetails' signing certificate history, including the current
* signer, and whether or not it has the given permission. Certificates which match our
* current signer automatically get all capabilities. Automatically returns false if this
* object has multiple signing certificates, since rotation is only supported for
* single-signers.
*/
public boolean hasSha256Certificate(byte[] sha256Certificate, @CertCapabilities int flags) {
return hasSha256CertificateInternal(sha256Certificate, flags);
}
private boolean hasSha256CertificateInternal(byte[] sha256Certificate, int flags) {
if (this == UNKNOWN) {
return false;
}
if (hasPastSigningCertificates()) {
// check all past certs, except for the last one, which automatically gets all
// capabilities, since it is the same as the current signature, and is checked below
for (int i = 0; i < mPastSigningCertificates.length - 1; i++) {
byte[] digest = PackageUtils.computeSha256DigestBytes(
mPastSigningCertificates[i].toByteArray());
if (Arrays.equals(sha256Certificate, digest)) {
if (flags == PAST_CERT_EXISTS
|| (flags & mPastSigningCertificates[i].getFlags()) == flags) {
return true;
}
}
}
}
// not in previous certs signing history, just check the current signer
if (mSignatures.length == 1) {
byte[] digest = PackageUtils.computeSha256DigestBytes(mSignatures[0].toByteArray());
return Arrays.equals(sha256Certificate, digest);
}
return false;
}
/** Returns true if the signatures in this and other match exactly. */
public boolean signaturesMatchExactly(@NonNull SigningDetails other) {
return Signature.areExactMatch(this, other);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
boolean isUnknown = UNKNOWN == this;
dest.writeBoolean(isUnknown);
if (isUnknown) {
return;
}
dest.writeTypedArray(mSignatures, flags);
dest.writeInt(mSignatureSchemeVersion);
dest.writeArraySet(mPublicKeys);
dest.writeTypedArray(mPastSigningCertificates, flags);
}
protected SigningDetails(@NonNull Parcel in) {
final ClassLoader boot = Object.class.getClassLoader();
mSignatures = in.createTypedArray(Signature.CREATOR);
mSignatureSchemeVersion = in.readInt();
mPublicKeys = (ArraySet<PublicKey>) in.readArraySet(boot);
mPastSigningCertificates = in.createTypedArray(Signature.CREATOR);
}
public static final @NonNull Parcelable.Creator<SigningDetails> CREATOR =
new Creator<SigningDetails>() {
@Override
public SigningDetails createFromParcel(@NonNull Parcel source) {
if (source.readBoolean()) {
return UNKNOWN;
}
return new SigningDetails(source);
}
@Override
public SigningDetails[] newArray(int size) {
return new SigningDetails[size];
}
};
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (!(o instanceof SigningDetails)) return false;
final SigningDetails that = (SigningDetails) o;
if (mSignatureSchemeVersion != that.mSignatureSchemeVersion) return false;
if (!Signature.areExactMatch(this, that)) return false;
if (mPublicKeys != null) {
if (!mPublicKeys.equals((that.mPublicKeys))) {
return false;
}
} else if (that.mPublicKeys != null) {
return false;
}
// can't use Signature.areExactMatch() because order matters with the past signing certs
if (!Arrays.equals(mPastSigningCertificates, that.mPastSigningCertificates)) {
return false;
}
// The capabilities for the past signing certs must match as well.
if (mPastSigningCertificates != null) {
for (int i = 0; i < mPastSigningCertificates.length; i++) {
if (mPastSigningCertificates[i].getFlags()
!= that.mPastSigningCertificates[i].getFlags()) {
return false;
}
}
}
return true;
}
@Override
public int hashCode() {
int result = +Arrays.hashCode(mSignatures);
result = 31 * result + mSignatureSchemeVersion;
result = 31 * result + (mPublicKeys != null ? mPublicKeys.hashCode() : 0);
result = 31 * result + Arrays.hashCode(mPastSigningCertificates);
return result;
}
/**
* Builder of {@code SigningDetails} instances.
*/
public static class Builder {
private @NonNull Signature[] mSignatures;
private @SignatureSchemeVersion int mSignatureSchemeVersion =
SignatureSchemeVersion.UNKNOWN;
private @Nullable Signature[] mPastSigningCertificates;
public Builder() {
}
/** get signing certificates used to sign the current APK */
public SigningDetails.Builder setSignatures(@NonNull Signature[] signatures) {
mSignatures = signatures;
return this;
}
/** set the signature scheme version used to sign the APK */
public SigningDetails.Builder setSignatureSchemeVersion(
@SignatureSchemeVersion int signatureSchemeVersion) {
mSignatureSchemeVersion = signatureSchemeVersion;
return this;
}
/** set the signing certificates by which the APK proved it can be authenticated */
public SigningDetails.Builder setPastSigningCertificates(
@Nullable Signature[] pastSigningCertificates) {
mPastSigningCertificates = pastSigningCertificates;
return this;
}
private void checkInvariants() {
// must have signatures and scheme version set
if (mSignatures == null) {
throw new IllegalStateException("SigningDetails requires the current signing"
+ " certificates.");
}
}
/** build a {@code SigningDetails} object */
public SigningDetails build()
throws CertificateException {
checkInvariants();
return new SigningDetails(mSignatures, mSignatureSchemeVersion,
mPastSigningCertificates);
}
}
/** Parses the public keys from the set of signatures. */
public static ArraySet<PublicKey> toSigningKeys(@NonNull Signature[] signatures)
throws CertificateException {
final ArraySet<PublicKey> keys = new ArraySet<>(signatures.length);
for (int i = 0; i < signatures.length; i++) {
keys.add(signatures[i].getPublicKey());
}
return keys;
}
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/SigningDetails.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
/**
* The signing certificates associated with this application package.
*/
@DataClass.Generated.Member
public @Nullable Signature[] getSignatures() {
return mSignatures;
}
/**
* The signature scheme version for this application package.
*/
@DataClass.Generated.Member
public @SignatureSchemeVersion int getSignatureSchemeVersion() {
return mSignatureSchemeVersion;
}
/**
* The public keys set for the certificates.
*/
@DataClass.Generated.Member
public @Nullable ArraySet<PublicKey> getPublicKeys() {
return mPublicKeys;
}
/**
* APK Signature Scheme v3 includes support for adding a proof-of-rotation record that
* contains two pieces of information:
* 1) the past signing certificates
* 2) the flags that APK wants to assign to each of the past signing certificates.
*
* This collection of {@code Signature} objects, each of which is formed from a former
* signing certificate of this APK before it was changed by signing certificate rotation,
* represents the first piece of information. It is the APK saying to the rest of the
* world: "hey if you trust the old cert, you can trust me!" This is useful, if for
* instance, the platform would like to determine whether or not to allow this APK to do
* something it would've allowed it to do under the old cert (like upgrade).
*/
@DataClass.Generated.Member
public @Nullable Signature[] getPastSigningCertificates() {
return mPastSigningCertificates;
}
@DataClass.Generated(
time = 1650058974710L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/SigningDetails.java",
inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.Nullable android.content.pm.Signature[] mSignatures\nprivate final @android.content.pm.SigningDetails.SignatureSchemeVersion int mSignatureSchemeVersion\nprivate final @android.annotation.Nullable android.util.ArraySet<java.security.PublicKey> mPublicKeys\nprivate final @android.annotation.Nullable android.content.pm.Signature[] mPastSigningCertificates\nprivate static final int PAST_CERT_EXISTS\npublic static final android.content.pm.SigningDetails UNKNOWN\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.SigningDetails> CREATOR\npublic @android.annotation.NonNull android.content.pm.SigningDetails mergeLineageWith(android.content.pm.SigningDetails)\npublic @android.annotation.NonNull android.content.pm.SigningDetails mergeLineageWith(android.content.pm.SigningDetails,int)\nprivate @android.annotation.NonNull android.content.pm.SigningDetails mergeLineageWithAncestorOrSelf(android.content.pm.SigningDetails,int)\npublic boolean hasCommonAncestor(android.content.pm.SigningDetails)\npublic boolean hasAncestorOrSelfWithDigest(java.util.Set<java.lang.String>)\nprivate @android.annotation.Nullable android.content.pm.SigningDetails getDescendantOrSelf(android.content.pm.SigningDetails)\npublic boolean hasSignatures()\npublic boolean hasPastSigningCertificates()\npublic boolean hasAncestorOrSelf(android.content.pm.SigningDetails)\npublic boolean hasAncestor(android.content.pm.SigningDetails)\npublic boolean hasCommonSignerWithCapability(android.content.pm.SigningDetails,int)\npublic boolean checkCapability(android.content.pm.SigningDetails,int)\npublic boolean checkCapabilityRecover(android.content.pm.SigningDetails,int)\npublic boolean hasCertificate(android.content.pm.Signature)\npublic boolean hasCertificate(android.content.pm.Signature,int)\npublic boolean hasCertificate(byte[])\nprivate boolean hasCertificateInternal(android.content.pm.Signature,int)\npublic boolean checkCapability(java.lang.String,int)\npublic boolean hasSha256Certificate(byte[])\npublic boolean hasSha256Certificate(byte[],int)\nprivate boolean hasSha256CertificateInternal(byte[],int)\npublic boolean signaturesMatchExactly(android.content.pm.SigningDetails)\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\npublic static android.util.ArraySet<java.security.PublicKey> toSigningKeys(android.content.pm.Signature[])\nclass SigningDetails extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.NonNull android.content.pm.Signature[] mSignatures\nprivate @android.content.pm.SigningDetails.SignatureSchemeVersion int mSignatureSchemeVersion\nprivate @android.annotation.Nullable android.content.pm.Signature[] mPastSigningCertificates\npublic android.content.pm.SigningDetails.Builder setSignatures(android.content.pm.Signature[])\npublic android.content.pm.SigningDetails.Builder setSignatureSchemeVersion(int)\npublic android.content.pm.SigningDetails.Builder setPastSigningCertificates(android.content.pm.Signature[])\nprivate void checkInvariants()\npublic android.content.pm.SigningDetails build()\nclass Builder extends java.lang.Object implements []\[email protected](genConstructor=false, genConstDefs=false, genParcelable=true, genAidl=false)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}