diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java new file mode 100644 index 0000000000000..584a058aaedec --- /dev/null +++ b/core/java/android/content/pm/SigningDetails.java @@ -0,0 +1,928 @@ +/* + * 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.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"; + + @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 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; + } + + /** 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 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} when one has the same or an ancestor signer of the other. + * + *

Merging two signing lineages will result in a new {@code SigningDetails} instance + * containing the longest common lineage with the most restrictive capabilities. 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: + * - lineage B merged with lineage A -> B returns lineage A -> B. + * - lineage A -> B merged with lineage B -> C returns lineage A -> B -> C + * - 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 + * lineage A -> B with both capabilities revoked for A. + * - 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. + * + * @param otherSigningDetails The {@code SigningDetails} you would like to merge with. + * @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) { + 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; + } + return descendantSigningDetails == this ? mergeLineageWithAncestorOrSelf( + otherSigningDetails) : otherSigningDetails.mergeLineageWithAncestorOrSelf(this); + } + + /** + * 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} you would like to merge with. + * @return Merged {@code SigningDetails} instance. + */ + private @NonNull SigningDetails mergeLineageWithAncestorOrSelf( + @NonNull SigningDetails otherSigningDetails) { + // 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 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 with the most restrictive + // capabilities of the two lineages. + Signature signature = mPastSigningCertificates[index--]; + Signature ancestorSignature = + otherSigningDetails.mPastSigningCertificates[otherIndex--]; + Signature mergedSignature = new Signature(signature); + int mergedCapabilities = signature.getFlags() & ancestorSignature.getFlags(); + if (signature.getFlags() != mergedCapabilities) { + capabilitiesModified = true; + mergedSignature.setFlags(mergedCapabilities); + } + 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. + * + *

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}. + * + *

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 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. + * + *

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. + * + *

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 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.mSignatures, mSignatures); + } + 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(mSignatures, other.mSignatures); + } + + @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) in.readArraySet(boot); + mPastSigningCertificates = in.createTypedArray(Signature.CREATOR); + } + + public static final @NonNull Parcelable.Creator CREATOR = + new Creator() { + @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(mSignatures, that.mSignatures)) 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. + 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 toSigningKeys(@NonNull Signature[] signatures) + throws CertificateException { + final ArraySet 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.22. + // + // 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 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 = 1616984092921L, + codegenVersion = "1.0.22", + 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 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 CREATOR\npublic @android.annotation.NonNull android.content.pm.SigningDetails mergeLineageWith(android.content.pm.SigningDetails)\nprivate @android.annotation.NonNull android.content.pm.SigningDetails mergeLineageWithAncestorOrSelf(android.content.pm.SigningDetails)\npublic boolean hasCommonAncestor(android.content.pm.SigningDetails)\npublic boolean hasAncestorOrSelfWithDigest(java.util.Set)\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 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 []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false, genParcelable=true, genAidl=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +}