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

Commit f4f1218d authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

V4 signature check refactors.

- expose all v2/v3 digests to compare against v4: this is more robust
and don't rely on a comment to maintain compatibility,
- renamings and cleanups.

Bug: 160605420
Test: refactoring CL

Change-Id: Ib46855e0e1985fae244bb8aabbb40a7231a3736d
parent 14758791
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -159,7 +159,7 @@ public class V4Signature {
     *
     * @param fileSize - size of the signed file (APK)
     */
    public static byte[] getSigningData(long fileSize, HashingInfo hashingInfo,
    public static byte[] getSignedData(long fileSize, HashingInfo hashingInfo,
            SigningInfo signingInfo) {
        final int size =
                4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize(
+8 −9
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4;
import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;

import android.util.ArrayMap;
@@ -213,11 +212,9 @@ public class ApkSignatureSchemeV2Verifier {
                    verityDigest, apk.length(), signatureInfo);
        }

        byte[] digest = pickBestDigestForV4(contentDigests);

        return new VerifiedSigner(
                signerCerts.toArray(new X509Certificate[signerCerts.size()][]),
                verityRootHash, digest);
                verityRootHash, contentDigests);
    }

    private static X509Certificate[] verifySigner(
@@ -339,8 +336,7 @@ public class ApkSignatureSchemeV2Verifier {
            } catch (CertificateException e) {
                throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
            }
            certificate = new VerbatimX509Certificate(
                    certificate, encodedCert);
            certificate = new VerbatimX509Certificate(certificate, encodedCert);
            certs.add(certificate);
        }

@@ -434,12 +430,15 @@ public class ApkSignatureSchemeV2Verifier {
        public final X509Certificate[][] certs;

        public final byte[] verityRootHash;
        public final byte[] digest;
        // Algorithm -> digest map of signed digests in the signature.
        // All these are verified if requested.
        public final Map<Integer, byte[]> contentDigests;

        public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash, byte[] digest) {
        public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash,
                Map<Integer, byte[]> contentDigests) {
            this.certs = certs;
            this.verityRootHash = verityRootHash;
            this.digest = digest;
            this.contentDigests = contentDigests;
        }

    }
+18 −15
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContent
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
import static android.util.apk.ApkSigningBlockUtils.pickBestDigestForV4;
import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;

import android.os.Build;
@@ -161,7 +160,7 @@ public class ApkSignatureSchemeV3Verifier {
            boolean doVerifyIntegrity) throws SecurityException, IOException {
        int signerCount = 0;
        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
        VerifiedSigner result = null;
        Pair<X509Certificate[], VerifiedProofOfRotation> result = null;
        CertificateFactory certFactory;
        try {
            certFactory = CertificateFactory.getInstance("X.509");
@@ -206,18 +205,17 @@ public class ApkSignatureSchemeV3Verifier {
            ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
        }

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

        result.digest = pickBestDigestForV4(contentDigests);

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

    private static VerifiedSigner verifySigner(
    private static Pair<X509Certificate[], VerifiedProofOfRotation> verifySigner(
            ByteBuffer signerBlock,
            Map<Integer, byte[]> contentDigests,
            CertificateFactory certFactory)
@@ -349,8 +347,7 @@ public class ApkSignatureSchemeV3Verifier {
            } catch (CertificateException e) {
                throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
            }
            certificate = new VerbatimX509Certificate(
                    certificate, encodedCert);
            certificate = new VerbatimX509Certificate(certificate, encodedCert);
            certs.add(certificate);
        }

@@ -382,8 +379,9 @@ public class ApkSignatureSchemeV3Verifier {

    private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;

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

@@ -421,7 +419,7 @@ public class ApkSignatureSchemeV3Verifier {
                    break;
            }
        }
        return new VerifiedSigner(certChain, por);
        return Pair.create(certChain, por);
    }

    private static VerifiedProofOfRotation verifyProofOfRotationStruct(
@@ -570,12 +568,17 @@ public class ApkSignatureSchemeV3Verifier {
        public final X509Certificate[] certs;
        public final VerifiedProofOfRotation por;

        public byte[] verityRootHash;
        public byte[] digest;
        public final byte[] verityRootHash;
        // Algorithm -> digest map of signed digests in the signature.
        // All these are verified if requested.
        public final Map<Integer, byte[]> contentDigests;

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

    }
+30 −7
Original line number Diff line number Diff line
@@ -16,12 +16,14 @@

package android.util.apk;

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;
import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;

import android.os.incremental.IncrementalManager;
import android.os.incremental.V4Signature;
import android.util.ArrayMap;
import android.util.Pair;

import java.io.ByteArrayInputStream;
@@ -42,6 +44,7 @@ import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Map;

/**
 * APK Signature Scheme v4 verifier.
@@ -79,13 +82,20 @@ public class ApkSignatureSchemeV4Verifier {
            throw new SignatureNotFoundException("Failed to read V4 signature.", e);
        }

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

        return verifySigner(signingInfo, signedData);
        // Populate digests enforced by IncFS driver.
        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
        contentDigests.put(convertToContentDigestType(hashingInfo.hashAlgorithm),
                hashingInfo.rawRootHash);

        return new VerifiedSigner(new Certificate[]{result.first}, result.second, contentDigests);
    }

    private static VerifiedSigner verifySigner(V4Signature.SigningInfo signingInfo,
    private static Pair<Certificate, byte[]> verifySigner(V4Signature.SigningInfo signingInfo,
            final byte[] signedData) throws SecurityException {
        if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
            throw new SecurityException("No supported signatures found");
@@ -145,21 +155,34 @@ public class ApkSignatureSchemeV4Verifier {
                    "Public key mismatch between certificate and signature record");
        }

        return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.apkDigest);
        return Pair.create(certificate, signingInfo.apkDigest);
    }

    private static int convertToContentDigestType(int hashAlgorithm) throws SecurityException {
        if (hashAlgorithm == V4Signature.HASHING_ALGORITHM_SHA256) {
            return CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
        }
        throw new SecurityException("Unsupported hashAlgorithm: " + hashAlgorithm);
    }

    /**
     * Verified APK Signature Scheme v4 signer, including V3 digest.
     * Verified APK Signature Scheme v4 signer, including V2/V3 digest.
     *
     * @hide for internal use only.
     */
    public static class VerifiedSigner {
        public final Certificate[] certs;
        public byte[] apkDigest;
        public final byte[] apkDigest;

        // Algorithm -> digest map of signed digests in the signature.
        // These are continuously enforced by the IncFS driver.
        public final Map<Integer, byte[]> contentDigests;

        public VerifiedSigner(Certificate[] certs, byte[] apkDigest) {
        public VerifiedSigner(Certificate[] certs, byte[] apkDigest,
                Map<Integer, byte[]> contentDigests) {
            this.certs = certs;
            this.apkDigest = apkDigest;
            this.contentDigests = contentDigests;
        }

    }
+14 −6
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import java.security.cert.CertificateEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.ZipEntry;

@@ -184,21 +185,21 @@ public class ApkSignatureVerifier {
            Signature[] signerSigs = convertToSignatures(signerCerts);

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

                try {
                    // v4 is an add-on and requires v2 or v3 signature to validate against its
                    // certificate and digest
                    ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer =
                            ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
                    nonstreamingDigest = v3Signer.digest;
                    nonstreamingDigests = v3Signer.contentDigests;
                    nonstreamingCerts = new Certificate[][]{v3Signer.certs};
                } catch (SignatureNotFoundException e) {
                    try {
                        ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
                                ApkSignatureSchemeV2Verifier.verify(apkPath, false);
                        nonstreamingDigest = v2Signer.digest;
                        nonstreamingDigests = v2Signer.contentDigests;
                        nonstreamingCerts = v2Signer.certs;
                    } catch (SignatureNotFoundException ee) {
                        throw new SecurityException(
@@ -220,8 +221,15 @@ public class ApkSignatureVerifier {
                    }
                }

                if (!ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest,
                boolean found = false;
                for (byte[] nonstreamingDigest : nonstreamingDigests.values()) {
                    if (ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest,
                            vSigner.apkDigest.length)) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    throw new SecurityException("APK digest in V4 signature does not match V2/V3");
                }
            }
Loading