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

Commit 616584b0 authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

v4.1: support v3.1 key rotation in v4 signature

Bug: 202011194
Fixes: 202011194
Test: atest PkgInstallSignatureVerificationTest
Change-Id: I63b067c077a4ee1af54e7081e7691b4091f229d2
parent e95dee49
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -505,8 +505,8 @@ public final class IncrementalStorage {

        final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
                signature.hashingInfo);
        final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
                signature.signingInfo);
        final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
                signature.signingInfos);

        if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
            throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm);
@@ -520,7 +520,7 @@ public final class IncrementalStorage {
        if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) {
            throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
        }
        if (signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
        if (signingInfos.signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
            throw new IOException(
                    "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
        }
+71 −8
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;

/**
 * V4 signature fields.
@@ -74,7 +75,7 @@ public class V4Signature {
    }

    /**
     * V4 signature data.
     * Signature data.
     */
    public static class SigningInfo {
        public final byte[] apkDigest;  // used to match with the corresponding APK
@@ -98,7 +99,13 @@ public class V4Signature {
         * Constructs SigningInfo from byte array.
         */
        public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
            ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
            return fromByteBuffer(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
        }

        /**
         * Constructs SigningInfo from byte buffer.
         */
        public static SigningInfo fromByteBuffer(ByteBuffer buffer) throws IOException {
            byte[] apkDigest = readBytes(buffer);
            byte[] certificate = readBytes(buffer);
            byte[] additionalData = readBytes(buffer);
@@ -110,6 +117,62 @@ public class V4Signature {
        }
    }

    /**
     * Optional signature data block with ID.
     */
    public static class SigningInfoBlock {
        public final int blockId;
        public final byte[] signingInfo;

        public SigningInfoBlock(int blockId, byte[] signingInfo) {
            this.blockId = blockId;
            this.signingInfo = signingInfo;
        }

        static SigningInfoBlock fromByteBuffer(ByteBuffer buffer) throws IOException {
            int blockId = buffer.getInt();
            byte[] signingInfo = readBytes(buffer);
            return new SigningInfoBlock(blockId, signingInfo);
        }
    }

    /**
     * V4 signature data.
     */
    public static class SigningInfos {
        // Default signature.
        public final SigningInfo signingInfo;
        // Additional signatures corresponding to extended V2/V3/V31 blocks.
        public final SigningInfoBlock[] signingInfoBlocks;

        public SigningInfos(SigningInfo signingInfo) {
            this.signingInfo = signingInfo;
            this.signingInfoBlocks = new SigningInfoBlock[0];
        }

        public SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks) {
            this.signingInfo = signingInfo;
            this.signingInfoBlocks = signingInfoBlocks;
        }

        /**
         * Constructs SigningInfos from byte array.
         */
        public static SigningInfos fromByteArray(byte[] bytes) throws IOException {
            ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
            SigningInfo signingInfo = SigningInfo.fromByteBuffer(buffer);
            if (!buffer.hasRemaining()) {
                return new SigningInfos(signingInfo);
            }
            ArrayList<SigningInfoBlock> signingInfoBlocks = new ArrayList<>(1);
            while (buffer.hasRemaining()) {
                signingInfoBlocks.add(SigningInfoBlock.fromByteBuffer(buffer));
            }
            return new SigningInfos(signingInfo,
                    signingInfoBlocks.toArray(new SigningInfoBlock[signingInfoBlocks.size()]));
        }
    }

    public final int version; // Always 2 for now.
    /**
     * Raw byte array containing the IncFS hashing data.
@@ -118,11 +181,11 @@ public class V4Signature {
    @Nullable public final byte[] hashingInfo;

    /**
     * Raw byte array containing the V4 signature data.
     * Raw byte array containing V4 signatures.
     * <p>Passed as-is to the kernel. Can be retrieved later.
     * @see SigningInfo#fromByteArray(byte[])
     * @see SigningInfos#fromByteArray(byte[])
     */
    @Nullable public final byte[] signingInfo;
    @Nullable public final byte[] signingInfos;

    /**
     * Construct a V4Signature from .idsig file.
@@ -185,10 +248,10 @@ public class V4Signature {
        return this.version == SUPPORTED_VERSION;
    }

    private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfo) {
    private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfos) {
        this.version = version;
        this.hashingInfo = hashingInfo;
        this.signingInfo = signingInfo;
        this.signingInfos = signingInfos;
    }

    private static V4Signature readFrom(InputStream stream) throws IOException {
@@ -205,7 +268,7 @@ public class V4Signature {
    private void writeTo(OutputStream stream) throws IOException {
        writeIntLE(stream, this.version);
        writeBytes(stream, this.hashingInfo);
        writeBytes(stream, this.signingInfo);
        writeBytes(stream, this.signingInfos);
    }

    // Utility methods.
+10 −4
Original line number Diff line number Diff line
@@ -70,8 +70,8 @@ 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;
    static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
    static final int APK_SIGNATURE_SCHEME_V31_BLOCK_ID = 0x1b93ad61;

    /**
     * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
@@ -260,7 +260,8 @@ public class ApkSignatureSchemeV3Verifier {
                    verityDigest, mApk.getChannel().size(), signatureInfo);
        }

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

    private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
@@ -572,13 +573,18 @@ public class ApkSignatureSchemeV3Verifier {
        // All these are verified if requested.
        public final Map<Integer, byte[]> contentDigests;

        // ID of the signature block used to verify.
        public final int blockId;

        public VerifiedSigner(X509Certificate[] certs,
                ApkSigningBlockUtils.VerifiedProofOfRotation por,
                byte[] verityRootHash, Map<Integer, byte[]> contentDigests) {
                byte[] verityRootHash, Map<Integer, byte[]> contentDigests,
                int blockId) {
            this.certs = certs;
            this.por = por;
            this.verityRootHash = verityRootHash;
            this.contentDigests = contentDigests;
            this.blockId = blockId;
        }

    }
+54 −9
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.util.apk;

import static android.util.apk.ApkSignatureSchemeV3Verifier.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
@@ -52,38 +53,60 @@ import java.util.Map;
 * @hide for internal use only.
 */
public class ApkSignatureSchemeV4Verifier {
    static final int APK_SIGNATURE_SCHEME_DEFAULT = 0xffffffff;

    /**
     * Extracts and verifies APK Signature Scheme v4 signatures of the provided APK and returns the
     * Extracts and verifies APK Signature Scheme v4 signature of the provided APK and returns the
     * certificates associated with each signer.
     */
    public static VerifiedSigner extractCertificates(String apkFile)
            throws SignatureNotFoundException, SecurityException {
        V4Signature signature = extractSignature(apkFile);
        return verify(apkFile, signature, APK_SIGNATURE_SCHEME_DEFAULT);
    }

    /**
     * Extracts APK Signature Scheme v4 signature of the provided APK.
     */
    public static V4Signature extractSignature(String apkFile) throws SignatureNotFoundException {
        final File apk = new File(apkFile);
        final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature(
                apk.getAbsolutePath());
        if (signatureBytes == null || signatureBytes.length == 0) {
            throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS.");
        }

        final V4Signature signature;
        final V4Signature.HashingInfo hashingInfo;
        final V4Signature.SigningInfo signingInfo;
        try {
            signature = V4Signature.readFrom(signatureBytes);

            final V4Signature signature = V4Signature.readFrom(signatureBytes);
            if (!signature.isVersionSupported()) {
                throw new SecurityException(
                        "v4 signature version " + signature.version + " is not supported");
            }
            return signature;
        } catch (IOException e) {
            throw new SignatureNotFoundException("Failed to read V4 signature.", e);
        }
    }

    /**
     * Verifies APK Signature Scheme v4 signature and returns the
     * certificates associated with each signer.
     */
    public static VerifiedSigner verify(String apkFile, final V4Signature signature,
            final int v3BlockId) throws SignatureNotFoundException, SecurityException {
        final V4Signature.HashingInfo hashingInfo;
        final V4Signature.SigningInfos signingInfos;
        try {
            hashingInfo = V4Signature.HashingInfo.fromByteArray(signature.hashingInfo);
            signingInfo = V4Signature.SigningInfo.fromByteArray(signature.signingInfo);
            signingInfos = V4Signature.SigningInfos.fromByteArray(signature.signingInfos);
        } catch (IOException e) {
            throw new SignatureNotFoundException("Failed to read V4 signature.", e);
        }

        final V4Signature.SigningInfo signingInfo = findSigningInfoForBlockId(signingInfos,
                v3BlockId);

        // Verify signed data and extract certificates and apk digest.
        final byte[] signedData = V4Signature.getSignedData(apk.length(), hashingInfo,
        final byte[] signedData = V4Signature.getSignedData(new File(apkFile).length(), hashingInfo,
                signingInfo);
        final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData);

@@ -95,6 +118,28 @@ public class ApkSignatureSchemeV4Verifier {
        return new VerifiedSigner(new Certificate[]{result.first}, result.second, contentDigests);
    }

    private static V4Signature.SigningInfo findSigningInfoForBlockId(
            final V4Signature.SigningInfos signingInfos, final int v3BlockId)
            throws SignatureNotFoundException {
        // Use default signingInfo for v3 block.
        if (v3BlockId == APK_SIGNATURE_SCHEME_DEFAULT
                || v3BlockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
            return signingInfos.signingInfo;
        }
        for (V4Signature.SigningInfoBlock signingInfoBlock : signingInfos.signingInfoBlocks) {
            if (v3BlockId == signingInfoBlock.blockId) {
                try {
                    return V4Signature.SigningInfo.fromByteArray(signingInfoBlock.signingInfo);
                } catch (IOException e) {
                    throw new SecurityException(
                            "Failed to read V4 signature block: " + signingInfoBlock.blockId, e);
                }
            }
        }
        throw new SecurityException(
                "Failed to find V4 signature block corresponding to V3 blockId: " + v3BlockId);
    }

    private static Pair<Certificate, byte[]> verifySigner(V4Signature.SigningInfo signingInfo,
            final byte[] signedData) throws SecurityException {
        if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
+42 −34
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTEN
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT;

import android.content.pm.Signature;
import android.content.pm.SigningDetails;
@@ -31,6 +32,7 @@ import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.os.Build;
import android.os.Trace;
import android.os.incremental.V4Signature;
import android.util.jar.StrictJarFile;

import com.android.internal.util.ArrayUtils;
@@ -189,16 +191,15 @@ public class ApkSignatureVerifier {
            boolean verifyFull) throws SignatureNotFoundException {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
        try {
            ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
                    ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
            Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
            Signature[] signerSigs = convertToSignatures(signerCerts);
            final V4Signature v4Signature = ApkSignatureSchemeV4Verifier.extractSignature(apkPath);

            Signature[] pastSignerSigs = null;

            if (verifyFull) {
            Map<Integer, byte[]> nonstreamingDigests;
            Certificate[][] nonstreamingCerts;

            int v3BlockId = APK_SIGNATURE_SCHEME_DEFAULT;

            try {
                // v4 is an add-on and requires v2 or v3 signature to validate against its
                // certificate and digest
@@ -215,6 +216,7 @@ public class ApkSignatureVerifier {
                        pastSignerSigs[i].setFlags(v3Signer.por.flagsList.get(i));
                    }
                }
                v3BlockId = v3Signer.blockId;
            } catch (SignatureNotFoundException e) {
                try {
                    ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
@@ -228,6 +230,12 @@ public class ApkSignatureVerifier {
                }
            }

            ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
                    ApkSignatureSchemeV4Verifier.verify(apkPath, v4Signature, v3BlockId);
            Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
            Signature[] signerSigs = convertToSignatures(signerCerts);

            if (verifyFull) {
                Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
                if (nonstreamingSigs.length != signerSigs.length) {
                    throw new SecurityException(
@@ -260,7 +268,7 @@ public class ApkSignatureVerifier {
        } catch (SignatureNotFoundException e) {
            throw e;
        } catch (Exception e) {
            // APK Signature Scheme v4 signature found but did not verify
            // APK Signature Scheme v4 signature found but did not verify.
            return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "Failed to collect certificates from " + apkPath
                            + " using APK Signature Scheme v4", e);