Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit daadf079 authored by Michael Groover's avatar Michael Groover
Browse files

Add support for APK Signature Scheme V3.1

This commit adds support for the V3.1 APK signature scheme; this
scheme allows an APK to target T+ for rotation without needing to
configure multi-targeting APKs. This scheme can be used by apps that
are impacted by any of the known issues with rotation on pre-T
releases. The v3.1 signature scheme uses a new block ID that is not
recognized by previous platforms, so earlier releases will use the
original signer in the v3.0 block, and T+ devices will use the
rotated signer in the v3.1 block. This scheme also supports SDK
version targeting allowing rotation to target a later release.

Bug: 192301300
Test: atest PkgInstallSignatureVerificationTest
Change-Id: I0b3345f0fd688aaaa1ef9f57d362d0fd0cb0565b
parent 54d0193e
Loading
Loading
Loading
Loading
+114 −14
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;

/**
 * APK Signature Scheme v3 verifier.
@@ -70,6 +71,7 @@ public class ApkSignatureSchemeV3Verifier {
    public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;

    private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
    private static final int APK_SIGNATURE_SCHEME_V31_BLOCK_ID = 0x1b93ad61;

    /**
     * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
@@ -133,8 +135,23 @@ public class ApkSignatureSchemeV3Verifier {
     */
    private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
            throws SignatureNotFoundException, SecurityException, IOException {
        SignatureInfo signatureInfo = findSignature(apk);
        return verify(apk, signatureInfo, verifyIntegrity);
        ApkSignatureSchemeV3Verifier verifier = new ApkSignatureSchemeV3Verifier(apk,
                verifyIntegrity);
        try {
            SignatureInfo signatureInfo = findSignature(apk, APK_SIGNATURE_SCHEME_V31_BLOCK_ID);
            return verifier.verify(signatureInfo, APK_SIGNATURE_SCHEME_V31_BLOCK_ID);
        } catch (SignatureNotFoundException ignored) {
            // This is expected if the APK is not using v3.1 to target rotation.
        } catch (PlatformNotSupportedException ignored) {
            // This is expected if the APK is targeting a platform version later than that of the
            // device for rotation.
        }
        try {
            SignatureInfo signatureInfo = findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
            return verifier.verify(signatureInfo, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
        } catch (PlatformNotSupportedException e) {
            throw new SecurityException(e);
        }
    }

    /**
@@ -146,7 +163,31 @@ public class ApkSignatureSchemeV3Verifier {
     */
    public static SignatureInfo findSignature(RandomAccessFile apk)
            throws IOException, SignatureNotFoundException {
        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
        return findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
    }

    /*
     * Returns the APK Signature Scheme v3 block in the provided {@code apk} file with the specified
     * {@code blockId} and the additional information relevant for verifying the block against the
     * file.
     *
     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3
     * @throws IOException                if an I/O error occurs while reading the APK file
     **/
    private static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
            throws IOException, SignatureNotFoundException {
        return ApkSigningBlockUtils.findSignature(apk, blockId);
    }

    private final RandomAccessFile mApk;
    private final boolean mVerifyIntegrity;
    private OptionalInt mOptionalRotationMinSdkVersion = OptionalInt.empty();
    private int mSignerMinSdkVersion;
    private int mBlockId;

    private ApkSignatureSchemeV3Verifier(RandomAccessFile apk, boolean verifyIntegrity) {
        mApk = apk;
        mVerifyIntegrity = verifyIntegrity;
    }

    /**
@@ -156,10 +197,9 @@ public class ApkSignatureSchemeV3Verifier {
     * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
     *                      against the APK file.
     */
    private static VerifiedSigner verify(
            RandomAccessFile apk,
            SignatureInfo signatureInfo,
            boolean doVerifyIntegrity) throws SecurityException, IOException {
    private VerifiedSigner verify(SignatureInfo signatureInfo, int blockId)
            throws SecurityException, IOException, PlatformNotSupportedException {
        mBlockId = blockId;
        int signerCount = 0;
        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
        Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> result = null;
@@ -191,8 +231,14 @@ public class ApkSignatureSchemeV3Verifier {
        }

        if (signerCount < 1 || result == null) {
            // There must always be a valid signer targeting the device SDK version for a v3.0
            // signature.
            if (blockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
                throw new SecurityException("No signers found");
            }
            throw new PlatformNotSupportedException(
                    "None of the signers support the current platform version");
        }

        if (signerCount != 1) {
            throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
@@ -203,21 +249,21 @@ public class ApkSignatureSchemeV3Verifier {
            throw new SecurityException("No content digests found");
        }

        if (doVerifyIntegrity) {
            ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
        if (mVerifyIntegrity) {
            ApkSigningBlockUtils.verifyIntegrity(contentDigests, mApk, signatureInfo);
        }

        byte[] verityRootHash = null;
        if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
            byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
            verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
                    verityDigest, apk.getChannel().size(), signatureInfo);
                    verityDigest, mApk.getChannel().size(), signatureInfo);
        }

        return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests);
    }

    private static Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
    private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
            verifySigner(
                ByteBuffer signerBlock,
                Map<Integer, byte[]> contentDigests,
@@ -228,6 +274,14 @@ public class ApkSignatureSchemeV3Verifier {
        int maxSdkVersion = signerBlock.getInt();

        if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
            // if this is a v3.1 block then save the minimum SDK version for rotation for comparison
            // against the v3.0 additional attribute.
            if (mBlockId == APK_SIGNATURE_SCHEME_V31_BLOCK_ID) {
                if (!mOptionalRotationMinSdkVersion.isPresent()
                        || mOptionalRotationMinSdkVersion.getAsInt() > minSdkVersion) {
                    mOptionalRotationMinSdkVersion = OptionalInt.of(minSdkVersion);
                }
            }
            // this signature isn't meant to be used with this platform, skip it.
            throw new PlatformNotSupportedException(
                    "Signer not supported by this platform "
@@ -370,6 +424,7 @@ public class ApkSignatureSchemeV3Verifier {
            throw new SecurityException(
                    "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
        }
        mSignerMinSdkVersion = signedMinSDK;

        int signedMaxSDK = signedData.getInt();
        if (signedMaxSDK != maxSdkVersion) {
@@ -382,10 +437,12 @@ public class ApkSignatureSchemeV3Verifier {
    }

    private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
    private static final int ROTATION_MIN_SDK_VERSION_ATTR_ID = 0x559f8b02;
    private static final int ROTATION_ON_DEV_RELEASE_ATTR_ID = 0xc2a6b3ba;

    private static Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
    private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
            verifyAdditionalAttributes(ByteBuffer attrs, List<X509Certificate> certs,
                CertificateFactory certFactory) throws IOException {
                CertificateFactory certFactory) throws IOException, PlatformNotSupportedException {
        X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
        ApkSigningBlockUtils.VerifiedProofOfRotation por = null;

@@ -417,6 +474,49 @@ public class ApkSignatureSchemeV3Verifier {
                                + " Proof-of-rotation record and signing certificate", e);
                    }

                    break;
                case ROTATION_MIN_SDK_VERSION_ATTR_ID:
                    if (attr.remaining() < 4) {
                        throw new IOException(
                                "Remaining buffer too short to contain rotation minSdkVersion "
                                        + "value. Remaining: "
                                        + attr.remaining());
                    }
                    int attrRotationMinSdkVersion = attr.getInt();
                    if (!mOptionalRotationMinSdkVersion.isPresent()) {
                        throw new SecurityException(
                                "Expected a v3.1 signing block targeting SDK version "
                                        + attrRotationMinSdkVersion
                                        + ", but a v3.1 block was not found");
                    }
                    int rotationMinSdkVersion = mOptionalRotationMinSdkVersion.getAsInt();
                    if (rotationMinSdkVersion != attrRotationMinSdkVersion) {
                        throw new SecurityException(
                                "Expected a v3.1 signing block targeting SDK version "
                                        + attrRotationMinSdkVersion
                                        + ", but the v3.1 block was targeting "
                                        + rotationMinSdkVersion);
                    }
                    break;
                case ROTATION_ON_DEV_RELEASE_ATTR_ID:
                    // This attribute should only be used in a v3.1 signer as it allows the v3.1
                    // signature scheme to target a platform under development.
                    if (mBlockId == APK_SIGNATURE_SCHEME_V31_BLOCK_ID) {
                        // A platform under development uses the same SDK version as the most
                        // recently released platform; if this platform's SDK version is the same as
                        // the minimum of the signer, then only allow this signer to be used if this
                        // is not a "REL" platform.
                        if (Build.VERSION.SDK_INT == mSignerMinSdkVersion
                                && "REL".equals(Build.VERSION.CODENAME)) {
                            // Set the rotation-min-sdk-version to be verified against the stripping
                            // attribute in the v3.0 block.
                            mOptionalRotationMinSdkVersion = OptionalInt.of(mSignerMinSdkVersion);
                            throw new PlatformNotSupportedException(
                                    "The device is running a release version of "
                                            + mSignerMinSdkVersion
                                            + ", but the signer is targeting a dev release");
                        }
                    }
                    break;
                default:
                    // not the droid we're looking for, move along, move along.