From 07bc80c51b1c099bd1ac5f3056ae739778753af1 Mon Sep 17 00:00:00 2001 From: Victor Hsieh Date: Thu, 11 Jan 2018 16:15:47 -0800 Subject: [PATCH] ApkSignatureVerifier: expose verity interfaces Test: the verity install request goes through to installd Bug: 30972906 Change-Id: I3683d6483880291200cd963b7aa4da81c8097724 --- .../util/apk/ApkSignatureSchemeV2Verifier.java | 57 +++++++++++++++++++--- .../util/apk/ApkSignatureSchemeV3Verifier.java | 26 ++++++++++ .../android/util/apk/ApkSignatureVerifier.java | 49 +++++++++++++++++++ .../android/util/apk/ApkSigningBlockUtils.java | 20 ++++++++ 4 files changed, 146 insertions(+), 6 deletions(-) diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index ce8998fcb863..5a09dab552e3 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -16,6 +16,7 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; @@ -42,6 +43,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -105,7 +107,8 @@ public class ApkSignatureSchemeV2Verifier { */ public static X509Certificate[][] verify(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { - return verify(apkFile, true); + VerifiedSigner vSigner = verify(apkFile, true); + return vSigner.certs; } /** @@ -119,10 +122,11 @@ public class ApkSignatureSchemeV2Verifier { */ public static X509Certificate[][] plsCertsNoVerifyOnlyCerts(String apkFile) throws SignatureNotFoundException, SecurityException, IOException { - return verify(apkFile, false); + VerifiedSigner vSigner = verify(apkFile, false); + return vSigner.certs; } - private static X509Certificate[][] verify(String apkFile, boolean verifyIntegrity) + private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { return verify(apk, verifyIntegrity); @@ -138,7 +142,7 @@ public class ApkSignatureSchemeV2Verifier { * verify. * @throws IOException if an I/O error occurs while reading the APK file. */ - private static X509Certificate[][] verify(RandomAccessFile apk, boolean verifyIntegrity) + private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { SignatureInfo signatureInfo = findSignature(apk); return verify(apk, signatureInfo, verifyIntegrity); @@ -163,7 +167,7 @@ public class ApkSignatureSchemeV2Verifier { * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it * against the APK file. */ - private static X509Certificate[][] verify( + private static VerifiedSigner verify( RandomAccessFile apk, SignatureInfo signatureInfo, boolean doVerifyIntegrity) throws SecurityException, IOException { @@ -207,7 +211,14 @@ public class ApkSignatureSchemeV2Verifier { ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo); } - return signerCerts.toArray(new X509Certificate[signerCerts.size()][]); + byte[] verityRootHash = null; + if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { + verityRootHash = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); + } + + return new VerifiedSigner( + signerCerts.toArray(new X509Certificate[signerCerts.size()][]), + verityRootHash); } private static X509Certificate[] verifySigner( @@ -383,6 +394,24 @@ public class ApkSignatureSchemeV2Verifier { return; } + static byte[] getVerityRootHash(String apkPath) + throws IOException, SignatureNotFoundException, SecurityException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + VerifiedSigner vSigner = verify(apk, false); + return vSigner.verityRootHash; + } + } + + static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + return ApkSigningBlockUtils.generateApkVerity(apkPath, bufferFactory, signatureInfo); + } + } + private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { switch (sigAlgorithm) { case SIGNATURE_RSA_PSS_WITH_SHA256: @@ -400,4 +429,20 @@ public class ApkSignatureSchemeV2Verifier { return false; } } + + /** + * Verified APK Signature Scheme v2 signer. + * + * @hide for internal use only. + */ + public static class VerifiedSigner { + public final X509Certificate[][] certs; + public final byte[] verityRootHash; + + public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash) { + this.certs = certs; + this.verityRootHash = verityRootHash; + } + + } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index c9e67fe1c9ef..1b04eb2f7d44 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -16,6 +16,7 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; @@ -43,6 +44,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -211,6 +213,10 @@ public class ApkSignatureSchemeV3Verifier { ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo); } + if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { + result.verityRootHash = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); + } + return result; } @@ -499,6 +505,24 @@ public class ApkSignatureSchemeV3Verifier { return new VerifiedProofOfRotation(certs, flagsList); } + static byte[] getVerityRootHash(String apkPath) + throws IOException, SignatureNotFoundException, SecurityException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + VerifiedSigner vSigner = verify(apk, false); + return vSigner.verityRootHash; + } + } + + static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + SignatureInfo signatureInfo = findSignature(apk); + return ApkSigningBlockUtils.generateApkVerity(apkPath, bufferFactory, signatureInfo); + } + } + private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { switch (sigAlgorithm) { case SIGNATURE_RSA_PSS_WITH_SHA256: @@ -541,6 +565,8 @@ public class ApkSignatureSchemeV3Verifier { public final X509Certificate[] certs; public final VerifiedProofOfRotation por; + public byte[] verityRootHash; + public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) { this.certs = certs; this.por = por; diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 555c4740389a..a2a76169c83a 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -36,7 +36,9 @@ import libcore.io.IoUtils; import java.io.IOException; import java.io.InputStream; +import java.security.DigestException; import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; @@ -367,4 +369,51 @@ public class ApkSignatureVerifier { // v2 didn't work, try jarsigner return verifyV1Signature(apkPath, false); } + + /** + * @return the verity root hash in the Signing Block. + */ + public static byte[] getVerityRootHash(String apkPath) + throws IOException, SignatureNotFoundException, SecurityException { + // first try v3 + try { + return ApkSignatureSchemeV3Verifier.getVerityRootHash(apkPath); + } catch (SignatureNotFoundException e) { + // try older version + } + return ApkSignatureSchemeV2Verifier.getVerityRootHash(apkPath); + } + + /** + * Generates the Merkle tree and verity metadata to the buffer allocated by the {@code + * ByteBufferFactory}. + * + * @return the verity root hash of the generated Merkle tree. + */ + public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + // first try v3 + try { + return ApkSignatureSchemeV3Verifier.generateApkVerity(apkPath, bufferFactory); + } catch (SignatureNotFoundException e) { + // try older version + } + return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory); + } + + /** + * Result of a successful APK verification operation. + */ + public static class Result { + public final Certificate[][] certs; + public final Signature[] sigs; + public final int signatureSchemeVersion; + + public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { + this.certs = certs; + this.sigs = sigs; + this.signatureSchemeVersion = signingVersion; + } + } } diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 9d53847f8110..4146f6fab011 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -306,6 +306,26 @@ final class ApkSigningBlockUtils { } /** + * Generates the fsverity header and hash tree to be used by kernel for the given apk. This + * method does not check whether the root hash exists in the Signing Block or not. + * + *

The output is stored in the {@link ByteBuffer} created by the given {@link + * ByteBufferFactory}. + * + * @return the root hash of the generated hash tree. + */ + public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory, + SignatureInfo signatureInfo) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { + ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateApkVerity(apk, + signatureInfo, bufferFactory); + return result.rootHash; + } + } + + /** * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. * * @throws IOException if an I/O error occurs while reading the file. -- 2.11.0