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

Commit 93e28906 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Accept APK install with v4 signature to set up fs-verity" into main

parents aa031e39 760f1e4f
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -254,7 +254,10 @@ public class V4Signature {
        this.signingInfos = signingInfos;
    }

    private static V4Signature readFrom(InputStream stream) throws IOException {
    /**
     * Constructs a V4Signature from an InputStream.
     */
    public static V4Signature readFrom(InputStream stream) throws IOException {
        final int version = readIntLE(stream);
        int maxSize = INCFS_MAX_SIGNATURE_SIZE;
        final byte[] hashingInfo = readBytes(stream, maxSize);
+55 −11
Original line number Diff line number Diff line
@@ -27,9 +27,14 @@ import android.os.incremental.V4Signature;
import android.util.ArrayMap;
import android.util.Pair;

import com.android.internal.security.VerityUtils;

import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
@@ -60,7 +65,7 @@ public class ApkSignatureSchemeV4Verifier {
     * certificates associated with each signer.
     */
    public static VerifiedSigner extractCertificates(String apkFile)
            throws SignatureNotFoundException, SecurityException {
            throws SignatureNotFoundException, SignatureException, SecurityException {
        Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> pair = extractSignature(apkFile);
        return verify(apkFile, pair.first, pair.second, APK_SIGNATURE_SCHEME_DEFAULT);
    }
@@ -69,15 +74,37 @@ public class ApkSignatureSchemeV4Verifier {
     * Extracts APK Signature Scheme v4 signature of the provided APK.
     */
    public static Pair<V4Signature.HashingInfo, V4Signature.SigningInfos> extractSignature(
            String apkFile) throws SignatureNotFoundException {
            String apkFile) throws SignatureNotFoundException, SignatureException {
        try {
            final File apk = new File(apkFile);
        final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature(
            boolean needsConsistencyCheck;

            // 1. Try IncFS first. IncFS verifies the file according to the integrity metadata
            // (including the root hash of Merkle tree) it keeps track of with signature check. No
            // further consistentcy check is needed.
            byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature(
                    apk.getAbsolutePath());
        if (signatureBytes == null || signatureBytes.length == 0) {
            throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS.");
            V4Signature signature;
            if (signatureBytes != null && signatureBytes.length > 0) {
                needsConsistencyCheck = false;
                signature = V4Signature.readFrom(signatureBytes);
            } else if (android.security.Flags.extendVbChainToUpdatedApk()) {
                // 2. Try fs-verity next. fs-verity checks against the Merkle tree, but the
                // v4 signature file (including a raw root hash) is managed separately. We need to
                // ensure the signed data from the file is consistent with the actual file.
                needsConsistencyCheck = true;

                final File idsig = new File(apk.getAbsolutePath() + V4Signature.EXT);
                try (var fis = new FileInputStream(idsig.getAbsolutePath())) {
                    signature = V4Signature.readFrom(fis);
                } catch (IOException e) {
                    throw new SignatureNotFoundException(
                            "Failed to obtain signature bytes from .idsig");
                }
            } else {
                throw new SignatureNotFoundException(
                        "Failed to obtain signature bytes from IncFS.");
            }
        try {
            final V4Signature signature = V4Signature.readFrom(signatureBytes);
            if (!signature.isVersionSupported()) {
                throw new SecurityException(
                        "v4 signature version " + signature.version + " is not supported");
@@ -86,9 +113,26 @@ public class ApkSignatureSchemeV4Verifier {
                    signature.hashingInfo);
            final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
                    signature.signingInfos);

            if (needsConsistencyCheck) {
                final byte[] actualDigest = VerityUtils.getFsverityDigest(apk.getAbsolutePath());
                if (actualDigest == null) {
                    throw new SecurityException("The APK does not have fs-verity");
                }
                final byte[] computedDigest =
                        VerityUtils.generateFsVerityDigest(apk.length(), hashingInfo);
                if (!Arrays.equals(computedDigest, actualDigest)) {
                    throw new SignatureException("Actual digest does not match the v4 signature");
                }
            }

            return Pair.create(hashingInfo, signingInfos);
        } catch (EOFException e) {
            throw new SignatureException("V4 signature is invalid.", e);
        } catch (IOException e) {
            throw new SignatureNotFoundException("Failed to read V4 signature.", e);
        } catch (DigestException | NoSuchAlgorithmException e) {
            throw new SecurityException("Failed to calculate the digest", e);
        }
    }

@@ -107,7 +151,7 @@ public class ApkSignatureSchemeV4Verifier {
                signingInfo);
        final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData);

        // Populate digests enforced by IncFS driver.
        // Populate digests enforced by IncFS driver and fs-verity.
        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
        contentDigests.put(convertToContentDigestType(hashingInfo.hashAlgorithm),
                hashingInfo.rawRootHash);
@@ -217,7 +261,7 @@ public class ApkSignatureSchemeV4Verifier {
        public final byte[] apkDigest;

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

        public VerifiedSigner(Certificate[] certs, byte[] apkDigest,
+35 −2
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.internal.security;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.os.SystemProperties;
import android.os.incremental.V4Signature;
import android.system.Os;
import android.system.OsConstants;
import android.util.Slog;
@@ -40,6 +42,9 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@@ -192,9 +197,9 @@ public abstract class VerityUtils {
     *
     * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#file-digest-computation">
     *      File digest computation in Linux kernel documentation</a>
     * @return Bytes of fs-verity digest
     * @return Bytes of fs-verity digest, or null if the file does not have fs-verity enabled
     */
    public static byte[] getFsverityDigest(@NonNull String filePath) {
    public static @Nullable byte[] getFsverityDigest(@NonNull String filePath) {
        byte[] result = new byte[HASH_SIZE_BYTES];
        int retval = measureFsverityNative(filePath, result);
        if (retval < 0) {
@@ -206,6 +211,34 @@ public abstract class VerityUtils {
        return result;
    }

    /**
     * Generates an fs-verity digest from a V4Signature.HashingInfo and the file's size.
     */
    public static @NonNull byte[] generateFsVerityDigest(long fileSize,
            @NonNull V4Signature.HashingInfo hashingInfo)
            throws DigestException, NoSuchAlgorithmException {
        if (hashingInfo.rawRootHash == null || hashingInfo.rawRootHash.length != 32) {
            throw new IllegalArgumentException("Expect a 32-byte rootHash for SHA256");
        }
        if (hashingInfo.log2BlockSize != 12) {
            throw new IllegalArgumentException(
                    "Unsupported log2BlockSize: " + hashingInfo.log2BlockSize);
        }

        var buffer = ByteBuffer.allocate(256);  // sizeof(fsverity_descriptor)
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        buffer.put((byte) 1);                   // version
        buffer.put((byte) 1);                   // Merkle tree hash algorithm, 1 for SHA256
        buffer.put(hashingInfo.log2BlockSize);  // log2(block-size), only log2(4096) is supported
        buffer.put((byte) 0);                   // size of salt in bytes; 0 if none
        buffer.putInt(0);                       // reserved, must be 0
        buffer.putLong(fileSize);               // size of file the Merkle tree is built over
        buffer.put(hashingInfo.rawRootHash);    // Merkle tree root hash
        // The rest are zeros, including the latter half of root hash unused for SHA256.

        return MessageDigest.getInstance("SHA-256").digest(buffer.array());
    }

    /** @hide */
    @VisibleForTesting
    public static byte[] toFormattedDigest(byte[] digest) {
+1 −0
Original line number Diff line number Diff line
@@ -513,6 +513,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
                List<String> apkFiles =
                        filesList
                                .map(path -> path.toAbsolutePath().toString())
                                .filter(str -> str.endsWith(".apk"))
                                .collect(Collectors.toList());
                sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles);
            } catch (IOException e) {
+1 −1
Original line number Diff line number Diff line
@@ -655,7 +655,7 @@ public class ApkChecksums {
            }
        } catch (SignatureNotFoundException e) {
            // Nothing
        } catch (SecurityException e) {
        } catch (SignatureException | SecurityException e) {
            Slog.e(TAG, "V4 signature error", e);
        }
        return null;
Loading