Loading core/java/android/os/incremental/V4Signature.java +4 −1 Original line number Diff line number Diff line Loading @@ -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); Loading core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +55 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading @@ -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"); Loading @@ -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); } } Loading @@ -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); Loading Loading @@ -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, Loading core/java/com/android/internal/security/VerityUtils.java +35 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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) { Loading services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +1 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading services/core/java/com/android/server/pm/ApkChecksums.java +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
core/java/android/os/incremental/V4Signature.java +4 −1 Original line number Diff line number Diff line Loading @@ -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); Loading
core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +55 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading @@ -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"); Loading @@ -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); } } Loading @@ -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); Loading Loading @@ -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, Loading
core/java/com/android/internal/security/VerityUtils.java +35 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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) { Loading
services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +1 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading
services/core/java/com/android/server/pm/ApkChecksums.java +1 −1 Original line number Diff line number Diff line Loading @@ -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