From 6b7efbc4b9ba663f08b2c2164a747c4e642790df Mon Sep 17 00:00:00 2001 From: Alex Buynytskyy Date: Mon, 23 Mar 2020 18:23:15 -0700 Subject: [PATCH] V4 uses V2 digest if V3 is not available. Test: atest PackageManagerShellCommandIncrementalTest Bug: b/151240006 Change-Id: I242b599e434880ce218537574e879e9436e5d3da --- .../android/os/incremental/V4Signature.java | 14 +++---- .../apk/ApkSignatureSchemeV2Verifier.java | 17 ++++++-- .../apk/ApkSignatureSchemeV3Verifier.java | 18 +-------- .../apk/ApkSignatureSchemeV4Verifier.java | 8 ++-- .../util/apk/ApkSignatureVerifier.java | 40 ++++++++++++++----- .../util/apk/ApkSigningBlockUtils.java | 19 +++++++++ 6 files changed, 74 insertions(+), 42 deletions(-) diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 71f931da1a926..5cc73caa4f601 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -72,16 +72,16 @@ public class V4Signature { * V4 signature data. */ public static class SigningInfo { - public final byte[] v3Digest; // used to match with the corresponding APK + public final byte[] apkDigest; // used to match with the corresponding APK public final byte[] certificate; // ASN.1 DER form public final byte[] additionalData; // a free-form binary data blob public final byte[] publicKey; // ASN.1 DER, must match the certificate public final int signatureAlgorithmId; // see the APK v2 doc for the list public final byte[] signature; - SigningInfo(byte[] v3Digest, byte[] certificate, byte[] additionalData, + SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData, byte[] publicKey, int signatureAlgorithmId, byte[] signature) { - this.v3Digest = v3Digest; + this.apkDigest = apkDigest; this.certificate = certificate; this.additionalData = additionalData; this.publicKey = publicKey; @@ -94,13 +94,13 @@ public class V4Signature { */ public static SigningInfo fromByteArray(byte[] bytes) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); - byte[] v3Digest = readBytes(buffer); + byte[] apkDigest = readBytes(buffer); byte[] certificate = readBytes(buffer); byte[] additionalData = readBytes(buffer); byte[] publicKey = readBytes(buffer); int signatureAlgorithmId = buffer.getInt(); byte[] signature = readBytes(buffer); - return new SigningInfo(v3Digest, certificate, additionalData, publicKey, + return new SigningInfo(apkDigest, certificate, additionalData, publicKey, signatureAlgorithmId, signature); } } @@ -150,7 +150,7 @@ public class V4Signature { final int size = 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize( hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize( - signingInfo.v3Digest) + bytesSize(signingInfo.certificate) + bytesSize( + signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize( signingInfo.additionalData); ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(size); @@ -159,7 +159,7 @@ public class V4Signature { buffer.put(hashingInfo.log2BlockSize); writeBytes(buffer, hashingInfo.salt); writeBytes(buffer, hashingInfo.rawRootHash); - writeBytes(buffer, signingInfo.v3Digest); + writeBytes(buffer, signingInfo.apkDigest); writeBytes(buffer, signingInfo.certificate); writeBytes(buffer, signingInfo.additionalData); return buffer.array(); diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index 04be71f2babc0..346fe293d7aee 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -24,6 +24,7 @@ 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; @@ -117,7 +118,10 @@ public class ApkSignatureSchemeV2Verifier { return vSigner.certs; } - private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) + /** + * Same as above returns the full signer object, containing additional info e.g. digest. + */ + public static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { return verify(apk, verifyIntegrity); @@ -209,9 +213,11 @@ public class ApkSignatureSchemeV2Verifier { verityDigest, apk.length(), signatureInfo); } + byte[] digest = pickBestDigestForV4(contentDigests); + return new VerifiedSigner( signerCerts.toArray(new X509Certificate[signerCerts.size()][]), - verityRootHash); + verityRootHash, digest); } private static X509Certificate[] verifySigner( @@ -426,11 +432,14 @@ public class ApkSignatureSchemeV2Verifier { */ public static class VerifiedSigner { public final X509Certificate[][] certs; - public final byte[] verityRootHash; - public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash) { + public final byte[] verityRootHash; + public final byte[] digest; + + public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash, byte[] digest) { this.certs = certs; this.verityRootHash = verityRootHash; + this.digest = digest; } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index 2437af26770ba..4ab541b616ed4 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -16,8 +16,6 @@ package android.util.apk; -import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; -import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; @@ -26,6 +24,7 @@ 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; @@ -213,24 +212,11 @@ public class ApkSignatureSchemeV3Verifier { verityDigest, apk.length(), signatureInfo); } - result.digest = pickBestV3DigestForV4(contentDigests); + result.digest = pickBestDigestForV4(contentDigests); return result; } - // Keep in sync with pickBestV3DigestForV4 in apksigner.V3SchemeVerifier. - private static byte[] pickBestV3DigestForV4(Map contentDigests) { - final int[] orderedContentDigestTypes = - {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256, - CONTENT_DIGEST_CHUNKED_SHA256}; - for (int contentDigestType : orderedContentDigestTypes) { - if (contentDigests.containsKey(contentDigestType)) { - return contentDigests.get(contentDigestType); - } - } - return null; - } - private static VerifiedSigner verifySigner( ByteBuffer signerBlock, Map contentDigests, diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java index 8c240d99f5901..d40efce0b3b3c 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java @@ -145,7 +145,7 @@ public class ApkSignatureSchemeV4Verifier { "Public key mismatch between certificate and signature record"); } - return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.v3Digest); + return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.apkDigest); } /** @@ -155,11 +155,11 @@ public class ApkSignatureSchemeV4Verifier { */ public static class VerifiedSigner { public final Certificate[] certs; - public byte[] v3Digest; + public byte[] apkDigest; - public VerifiedSigner(Certificate[] certs, byte[] v3Digest) { + public VerifiedSigner(Certificate[] certs, byte[] apkDigest) { this.certs = certs; - this.v3Digest = v3Digest; + this.apkDigest = apkDigest; } } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index c1cee48cc6632..ab8f80d3d1a5d 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -184,27 +184,45 @@ public class ApkSignatureVerifier { Signature[] signerSigs = convertToSignatures(signerCerts); if (verifyFull) { - // v4 is an add-on and requires v3 signature to validate against its certificates - ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming = - ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); - Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs}; - Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); + byte[] nonstreamingDigest = null; + Certificate[][] nonstreamingCerts = null; + 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; + nonstreamingCerts = new Certificate[][]{v3Signer.certs}; + } catch (SignatureNotFoundException e) { + try { + ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer = + ApkSignatureSchemeV2Verifier.verify(apkPath, false); + nonstreamingDigest = v2Signer.digest; + nonstreamingCerts = v2Signer.certs; + } catch (SignatureNotFoundException ee) { + throw new SecurityException( + "V4 verification failed to collect V2/V3 certificates from : " + + apkPath, ee); + } + } + + Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); if (nonstreamingSigs.length != signerSigs.length) { throw new SecurityException( - "Invalid number of certificates: " + nonstreaming.certs.length); + "Invalid number of certificates: " + nonstreamingSigs.length); } for (int i = 0, size = signerSigs.length; i < size; ++i) { if (!nonstreamingSigs[i].equals(signerSigs[i])) { - throw new SecurityException("V4 signature certificate does not match V3"); + throw new SecurityException( + "V4 signature certificate does not match V2/V3"); } } - // TODO(b/151240006): add support for v2 digest and make it mandatory. - if (!ArrayUtils.isEmpty(vSigner.v3Digest) && !ArrayUtils.equals(vSigner.v3Digest, - nonstreaming.digest, vSigner.v3Digest.length)) { - throw new SecurityException("V3 digest in V4 signature does not match V3"); + if (!ArrayUtils.equals(vSigner.apkDigest, nonstreamingDigest, + vSigner.apkDigest.length)) { + throw new SecurityException("APK digest in V4 signature does not match V2/V3"); } } diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 4fe8515143a0e..2a4b65d23e647 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -421,6 +421,10 @@ final class ApkSigningBlockUtils { static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3; + private static final int[] V4_CONTENT_DIGEST_ALGORITHMS = + {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256, + CONTENT_DIGEST_CHUNKED_SHA256}; + static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); @@ -571,6 +575,21 @@ final class ApkSigningBlockUtils { } } + /** + * Returns the best digest from the map of available digests. + * similarly to compareContentDigestAlgorithm. + * + * Keep in sync with pickBestDigestForV4 in apksigner's ApkSigningBlockUtils. + */ + static byte[] pickBestDigestForV4(Map contentDigests) { + for (int algo : V4_CONTENT_DIGEST_ALGORITHMS) { + if (contentDigests.containsKey(algo)) { + return contentDigests.get(algo); + } + } + return null; + } + /** * Returns new byte buffer whose content is a shared subsequence of this buffer's content * between the specified start (inclusive) and end (exclusive) positions. As opposed to