/*
 * 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 []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false, genParcelable=true, genAidl=false)")
    @Deprecated
    private void __metadata() {}


    //@formatter:on
    // End of generated code

}
