From 4ba1eeaa0e0468131da08a5c5f461361cac79ff1 Mon Sep 17 00:00:00 2001 From: Victor Hsieh Date: Fri, 2 Mar 2018 14:26:19 -0800 Subject: Verify the content length in the verity digest When generating digest for verity, for the last incomplete 4k chunk, the data is padded with 0s. This implies that we can not tell from the digest whether the file contains 0 or not, or how many 0s. Since the verity hash is used by the kernel, the definition cannot be change. Instead, the actual hashed content length is appended to the original digest and is verified before used. Also uprev algorithm IDs. Test: use new apksigner to sign an apk, apk can be installed on device Bug: 30972906 Change-Id: I382af6e4090c7dc3f92d5acb5ac5d02d1f496992 --- .../util/apk/ApkSignatureSchemeV2Verifier.java | 4 +- .../util/apk/ApkSignatureSchemeV3Verifier.java | 6 ++- .../android/util/apk/ApkSigningBlockUtils.java | 43 ++++++++++++++++++++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 62222b5764e7..533d72590f0a 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -213,7 +213,9 @@ public class ApkSignatureSchemeV2Verifier { byte[] verityRootHash = null; if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { - verityRootHash = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); + byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); + verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength( + verityDigest, apk.length(), signatureInfo); } return new VerifiedSigner( diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 9436b296e9e2..4431bcef1ff4 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -165,7 +165,7 @@ public class ApkSignatureSchemeV3Verifier { private static VerifiedSigner verify( RandomAccessFile apk, SignatureInfo signatureInfo, - boolean doVerifyIntegrity) throws SecurityException { + boolean doVerifyIntegrity) throws SecurityException, IOException { int signerCount = 0; Map contentDigests = new ArrayMap<>(); VerifiedSigner result = null; @@ -214,7 +214,9 @@ public class ApkSignatureSchemeV3Verifier { } if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { - result.verityRootHash = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); + byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); + result.verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength( + verityDigest, apk.length(), signatureInfo); } return result; diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 40db758e1d4b..1c67434b2609 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -285,11 +285,46 @@ final class ApkSigningBlockUtils { return result; } + /** + * Return the verity digest only if the length of digest content looks correct. + * When verity digest is generated, the last incomplete 4k chunk is padded with 0s before + * hashing. This means two almost identical APKs with different number of 0 at the end will have + * the same verity digest. To avoid this problem, the length of the source content (excluding + * Signing Block) is appended to the verity digest, and the digest is returned only if the + * length is consistent to the current APK. + */ + static byte[] parseVerityDigestAndVerifySourceLength( + byte[] data, long fileSize, SignatureInfo signatureInfo) throws SecurityException { + // FORMAT: + // OFFSET DATA TYPE DESCRIPTION + // * @+0 bytes uint8[32] Merkle tree root hash of SHA-256 + // * @+32 bytes int64 Length of source data + int kRootHashSize = 32; + int kSourceLengthSize = 8; + + if (data.length != kRootHashSize + kSourceLengthSize) { + throw new SecurityException("Verity digest size is wrong: " + data.length); + } + ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + buffer.position(kRootHashSize); + long expectedSourceLength = buffer.getLong(); + + long signingBlockSize = signatureInfo.centralDirOffset + - signatureInfo.apkSigningBlockOffset; + if (expectedSourceLength != fileSize - signingBlockSize) { + throw new SecurityException("APK content size did not verify"); + } + + return Arrays.copyOfRange(data, 0, kRootHashSize); + } + private static void verifyIntegrityForVerityBasedAlgorithm( - byte[] expectedRootHash, + byte[] expectedDigest, RandomAccessFile apk, SignatureInfo signatureInfo) throws SecurityException { try { + byte[] expectedRootHash = parseVerityDigestAndVerifySourceLength(expectedDigest, + apk.length(), signatureInfo); ApkVerityBuilder.ApkVerityResult verity = ApkVerityBuilder.generateApkVerity(apk, signatureInfo, new ByteBufferFactory() { @Override @@ -373,9 +408,9 @@ final class ApkSigningBlockUtils { static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; - static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0411; - static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0413; - static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0415; + static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0421; + static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0423; + static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0425; static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; -- cgit v1.2.3-59-g8ed1b