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

Commit 036864b6 authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

v4 signing schema parsing and verification.

v4 is a streaming add-on to the existing v2/v3 schemas.

Flow:
- APK is signed with v2/v3 and v4 signature blocks,
- on installation, v4 signature bytes are stored next to the APK in
hidden block,
- on each read from APK, kernel verifies the v4 signature using
fs-verity-like code,
- on parsing/verification, we extract certificates from kernel and
compare them with certificates extracted from v2/v3 signature block.

By doing this we are making sure that v4 signature is produced by developer and original APK bytes are not changed.

Test: atest PkgInstallSignatureVerificationTest
Bug: b/136132412 b/133435829
Change-Id: Ia2a56c82c9864bf65e1338700dfe51abf6800deb
parent be020f68
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -61,7 +61,6 @@ import android.content.pm.PackageParserCacheHelper.WriteHelper;
import android.content.pm.parsing.AndroidPackage;
import android.content.pm.parsing.ApkParseUtils;
import android.content.pm.parsing.PackageImpl;
import android.content.pm.parsing.PackageInfoUtils;
import android.content.pm.parsing.ParsedPackage;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.content.pm.split.DefaultSplitAssetLoader;
@@ -5967,12 +5966,14 @@ public class PackageParser {
        @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN,
                SigningDetails.SignatureSchemeVersion.JAR,
                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3})
                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
                SigningDetails.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;
        }

        @Nullable
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.util.apk;

import android.os.incremental.IncrementalManager;

import java.io.File;
import java.security.cert.Certificate;

import sun.security.pkcs.PKCS7;
import sun.security.pkcs.ParsingException;

/**
 * APK Signature Scheme v4 verifier.
 *
 * @hide for internal use only.
 */
public class ApkSignatureSchemeV4Verifier {

    /**
     * Extracts APK Signature Scheme v4 signatures of the provided APK and returns the certificates
     * associated with each signer.
     */
    public static Certificate[] extractCertificates(String apkFile)
            throws SignatureNotFoundException, SecurityException {
        final byte[] rawSignature = IncrementalManager.unsafeGetFileSignature(
                new File(apkFile).getAbsolutePath());
        if (rawSignature == null || rawSignature.length == 0) {
            throw new SignatureNotFoundException("Failed to obtain raw signature from IncFS.");
        }

        try {
            PKCS7 pkcs7 = new PKCS7(rawSignature);
            return pkcs7.getCertificates();
        } catch (ParsingException e) {
            throw new SecurityException("Failed to parse signature and extract certificates", e);
        }
    }
}
+78 −1
Original line number Diff line number Diff line
@@ -92,6 +92,24 @@ public class ApkSignatureVerifier {
            @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
            throws PackageParserException {

        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
            // V3 and before are older than the requested minimum signing version
            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "No signature found in package of version " + minSignatureSchemeVersion
                            + " or newer for package " + apkPath);
        }

        // first try v4
        try {
            return verifyV4Signature(apkPath, minSignatureSchemeVersion, verifyFull);
        } catch (SignatureNotFoundException e) {
            // not signed with v4, try older if allowed
            if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                        "No APK Signature Scheme v4 signature in package " + apkPath, e);
            }
        }

        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
            // V3 and before are older than the requested minimum signing version
            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
@@ -99,7 +117,13 @@ public class ApkSignatureVerifier {
                            + " or newer for package " + apkPath);
        }

        // first try v3
        return verifyV3AndBelowSignatures(apkPath, minSignatureSchemeVersion, verifyFull);
    }

    private static PackageParser.SigningDetails verifyV3AndBelowSignatures(String apkPath,
            @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
            throws PackageParserException {
        // try v3
        try {
            return verifyV3Signature(apkPath, verifyFull);
        } catch (SignatureNotFoundException e) {
@@ -141,6 +165,59 @@ public class ApkSignatureVerifier {
        return verifyV1Signature(apkPath, verifyFull);
    }

    /**
     * Verifies the provided APK using V4 schema.
     *
     * @param verifyFull whether to verify all contents of this APK or just collect certificates.
     * @return the certificates associated with each signer.
     * @throws SignatureNotFoundException if there are no V4 signatures in the APK
     * @throws PackageParserException     if there was a problem collecting certificates
     */
    private static PackageParser.SigningDetails verifyV4Signature(String apkPath,
            @SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
            throws SignatureNotFoundException, PackageParserException {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
        try {
            Certificate[] certs = ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
            Certificate[][] signerCerts = new Certificate[][]{certs};
            Signature[] signerSigs = convertToSignatures(signerCerts);

            if (verifyFull) {
                // v4 is an add-on and requires v2/v3 signature to validate against its certificates
                final PackageParser.SigningDetails nonstreaming = verifyV3AndBelowSignatures(
                        apkPath, minSignatureSchemeVersion, false);
                if (nonstreaming.signatureSchemeVersion <= SignatureSchemeVersion.JAR) {
                    throw new SecurityException(
                            "V4 signing block can only be verified along with V2 and above.");
                }
                if (nonstreaming.signatures.length == 0
                        || nonstreaming.signatures.length != signerSigs.length) {
                    throw new SecurityException("Invalid number of signatures in "
                            + nonstreaming.signatureSchemeVersion);
                }

                for (int i = 0, size = signerSigs.length; i < size; ++i) {
                    if (!nonstreaming.signatures[i].equals(signerSigs[i])) {
                        throw new SecurityException("V4 signature certificate does not match "
                                + nonstreaming.signatureSchemeVersion);
                    }
                }
            }

            return new PackageParser.SigningDetails(signerSigs,
                    SignatureSchemeVersion.SIGNING_BLOCK_V4);
        } catch (SignatureNotFoundException e) {
            throw e;
        } catch (Exception e) {
            // APK Signature Scheme v4 signature found but did not verify
            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "Failed to collect certificates from " + apkPath
                            + " using APK Signature Scheme v4", e);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

    /**
     * Verifies the provided APK using V3 schema.
     *