Loading tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java +32 −11 Original line number Diff line number Diff line Loading @@ -56,16 +56,33 @@ public class ApkVerifier { * @param apk APK file contents * @param minSdkVersion API Level of the oldest Android platform on which the APK's signatures * may need to be verified * @param maxSdkVersion API Level of the newest Android platform on which the APK's signatures * may need to be verified * * @throws IOException if an I/O error is encountered while reading the APK * @throws ZipFormatException if the APK is malformed at ZIP format level */ public Result verify(DataSource apk, int minSdkVersion) throws IOException, ZipFormatException { public Result verify(DataSource apk, int minSdkVersion, int maxSdkVersion) throws IOException, ZipFormatException { if (minSdkVersion < 0) { throw new IllegalArgumentException( "minSdkVersion must not be negative: " + minSdkVersion); } if (minSdkVersion > maxSdkVersion) { throw new IllegalArgumentException( "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion + ")"); } ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk); // Attempt to verify the APK using APK Signature Scheme v2 Result result = new Result(); Set<Integer> foundApkSigSchemeIds = new HashSet<>(1); // Android N and newer attempts to verify APK Signature Scheme v2 signature of the APK. // If the signature is not found, it falls back to JAR signature verification. If the // signature is found but does not verify, the APK is rejected. Set<Integer> foundApkSigSchemeIds; if (maxSdkVersion >= AndroidSdkVersion.N) { foundApkSigSchemeIds = new HashSet<>(1); try { V2SchemeVerifier.Result v2Result = V2SchemeVerifier.verify(apk, zipSections); foundApkSigSchemeIds.add(APK_SIGNATURE_SCHEME_V2_ID); Loading @@ -74,6 +91,9 @@ public class ApkVerifier { if (result.containsErrors()) { return result; } } else { foundApkSigSchemeIds = Collections.emptySet(); } // Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N // ignore APK Signature Scheme v2 signatures and always attempt to verify JAR signatures. Loading @@ -86,7 +106,8 @@ public class ApkVerifier { zipSections, SUPPORTED_APK_SIG_SCHEME_NAMES, foundApkSigSchemeIds, minSdkVersion); minSdkVersion, maxSdkVersion); result.mergeFrom(v1Result); } if (result.containsErrors()) { Loading tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java +73 −34 Original line number Diff line number Diff line Loading @@ -78,7 +78,14 @@ public abstract class V1SchemeVerifier { ApkUtils.ZipSections apkSections, Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion) throws IOException, ZipFormatException { int minSdkVersion, int maxSdkVersion) throws IOException, ZipFormatException { if (minSdkVersion > maxSdkVersion) { throw new IllegalArgumentException( "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion + ")"); } Result result = new Result(); // Parse the ZIP Central Directory and check that there are no entries with duplicate names. Loading @@ -97,6 +104,7 @@ public abstract class V1SchemeVerifier { supportedApkSigSchemeNames, foundApkSigSchemeIds, minSdkVersion, maxSdkVersion, result); return result; Loading Loading @@ -143,6 +151,7 @@ public abstract class V1SchemeVerifier { Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion, int maxSdkVersion, Result result) throws ZipFormatException, IOException { // Find JAR manifest and signature block files. Loading Loading @@ -243,7 +252,8 @@ public abstract class V1SchemeVerifier { // signature file .SF. Any error encountered for any signer terminates verification, to // mimic Android's behavior. for (Signer signer : signers) { signer.verifySigBlockAgainstSigFile(apk, cdStartOffset, minSdkVersion); signer.verifySigBlockAgainstSigFile( apk, cdStartOffset, minSdkVersion, maxSdkVersion); if (signer.getResult().containsErrors()) { result.signers.add(signer.getResult()); } Loading @@ -264,7 +274,8 @@ public abstract class V1SchemeVerifier { entryNameToManifestSection, supportedApkSigSchemeNames, foundApkSigSchemeIds, minSdkVersion); minSdkVersion, maxSdkVersion); if (signer.isIgnored()) { result.ignoredSigners.add(signer.getResult()); } else { Loading Loading @@ -393,7 +404,7 @@ public abstract class V1SchemeVerifier { @SuppressWarnings("restriction") public void verifySigBlockAgainstSigFile( DataSource apk, long cdStartOffset, int minSdkVersion) DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion) throws IOException, ZipFormatException { byte[] sigBlockBytes = LocalFileHeader.getUncompressedData( Loading Loading @@ -433,7 +444,8 @@ public abstract class V1SchemeVerifier { String signatureAlgorithmOid = unverifiedSignerInfo .getDigestEncryptionAlgorithmId().getOID().toString(); InclusiveIntRange desiredApiLevels = InclusiveIntRange.from(minSdkVersion); InclusiveIntRange desiredApiLevels = InclusiveIntRange.fromTo(minSdkVersion, maxSdkVersion); List<InclusiveIntRange> apiLevelsWhereDigestAndSigAlgorithmSupported = getSigAlgSupportedApiLevels(digestAlgorithmOid, signatureAlgorithmOid); List<InclusiveIntRange> apiLevelsWhereDigestAlgorithmNotSupported = Loading Loading @@ -843,7 +855,8 @@ public abstract class V1SchemeVerifier { Map<String, ManifestParser.Section> entryNameToManifestSection, Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { // Inspect the main section of the .SF file. ManifestParser sf = new ManifestParser(mSigFileBytes); ManifestParser.Section sfMainSection = sf.readSection(); Loading @@ -854,11 +867,17 @@ public abstract class V1SchemeVerifier { setIgnored(); return; } if (maxSdkVersion >= AndroidSdkVersion.N) { // Android N and newer rejects APKs whose .SF file says they were supposed to be // signed with APK Signature Scheme v2 (or newer) and yet no such signature was // found. checkForStrippedApkSignatures( sfMainSection, supportedApkSigSchemeNames, foundApkSigSchemeIds); if (mResult.containsErrors()) { return; } } boolean createdBySigntool = false; String createdBy = sfMainSection.getAttributeValue("Created-By"); Loading @@ -867,10 +886,18 @@ public abstract class V1SchemeVerifier { } boolean manifestDigestVerified = verifyManifestDigest( sfMainSection, createdBySigntool, manifestBytes, minSdkVersion); sfMainSection, createdBySigntool, manifestBytes, minSdkVersion, maxSdkVersion); if (!createdBySigntool) { verifyManifestMainSectionDigest( sfMainSection, manifestMainSection, manifestBytes, minSdkVersion); sfMainSection, manifestMainSection, manifestBytes, minSdkVersion, maxSdkVersion); } if (mResult.containsErrors()) { return; Loading Loading @@ -922,7 +949,8 @@ public abstract class V1SchemeVerifier { createdBySigntool, manifestSection, manifestBytes, minSdkVersion); minSdkVersion, maxSdkVersion); } mSigFileEntryNames = sfEntryNames; } Loading @@ -936,12 +964,14 @@ public abstract class V1SchemeVerifier { ManifestParser.Section sfMainSection, boolean createdBySigntool, byte[] manifestBytes, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { Collection<NamedDigest> expectedDigests = getDigestsToVerify( sfMainSection, ((createdBySigntool) ? "-Digest" : "-Digest-Manifest"), minSdkVersion); minSdkVersion, maxSdkVersion); boolean digestFound = !expectedDigests.isEmpty(); if (!digestFound) { mResult.addWarning( Loading Loading @@ -977,10 +1007,14 @@ public abstract class V1SchemeVerifier { ManifestParser.Section sfMainSection, ManifestParser.Section manifestMainSection, byte[] manifestBytes, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { Collection<NamedDigest> expectedDigests = getDigestsToVerify( sfMainSection, "-Digest-Manifest-Main-Attributes", minSdkVersion); sfMainSection, "-Digest-Manifest-Main-Attributes", minSdkVersion, maxSdkVersion); if (expectedDigests.isEmpty()) { return; } Loading Loading @@ -1014,10 +1048,12 @@ public abstract class V1SchemeVerifier { boolean createdBySigntool, ManifestParser.Section manifestIndividualSection, byte[] manifestBytes, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { String entryName = sfIndividualSection.getName(); Collection<NamedDigest> expectedDigests = getDigestsToVerify(sfIndividualSection, "-Digest", minSdkVersion); getDigestsToVerify( sfIndividualSection, "-Digest", minSdkVersion, maxSdkVersion); if (expectedDigests.isEmpty()) { mResult.addError( Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE, Loading Loading @@ -1124,7 +1160,8 @@ public abstract class V1SchemeVerifier { private static Collection<NamedDigest> getDigestsToVerify( ManifestParser.Section section, String digestAttrSuffix, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { Decoder base64Decoder = Base64.getDecoder(); List<NamedDigest> result = new ArrayList<>(1); if (minSdkVersion < AndroidSdkVersion.JELLY_BEAN_MR2) { Loading Loading @@ -1163,7 +1200,8 @@ public abstract class V1SchemeVerifier { } } // JB MR2 and newer, Android platform picks the strongest algorithm out of: if (maxSdkVersion >= AndroidSdkVersion.JELLY_BEAN_MR2) { // On JB MR2 and newer, Android platform picks the strongest algorithm out of: // SHA-512, SHA-384, SHA-256, SHA-1. for (String alg : JB_MR2_AND_NEWER_DIGEST_ALGS) { String attrName = getJarDigestAttributeName(alg, digestAttrSuffix); Loading @@ -1179,6 +1217,7 @@ public abstract class V1SchemeVerifier { } break; } } return result; } Loading Loading
tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java +32 −11 Original line number Diff line number Diff line Loading @@ -56,16 +56,33 @@ public class ApkVerifier { * @param apk APK file contents * @param minSdkVersion API Level of the oldest Android platform on which the APK's signatures * may need to be verified * @param maxSdkVersion API Level of the newest Android platform on which the APK's signatures * may need to be verified * * @throws IOException if an I/O error is encountered while reading the APK * @throws ZipFormatException if the APK is malformed at ZIP format level */ public Result verify(DataSource apk, int minSdkVersion) throws IOException, ZipFormatException { public Result verify(DataSource apk, int minSdkVersion, int maxSdkVersion) throws IOException, ZipFormatException { if (minSdkVersion < 0) { throw new IllegalArgumentException( "minSdkVersion must not be negative: " + minSdkVersion); } if (minSdkVersion > maxSdkVersion) { throw new IllegalArgumentException( "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion + ")"); } ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk); // Attempt to verify the APK using APK Signature Scheme v2 Result result = new Result(); Set<Integer> foundApkSigSchemeIds = new HashSet<>(1); // Android N and newer attempts to verify APK Signature Scheme v2 signature of the APK. // If the signature is not found, it falls back to JAR signature verification. If the // signature is found but does not verify, the APK is rejected. Set<Integer> foundApkSigSchemeIds; if (maxSdkVersion >= AndroidSdkVersion.N) { foundApkSigSchemeIds = new HashSet<>(1); try { V2SchemeVerifier.Result v2Result = V2SchemeVerifier.verify(apk, zipSections); foundApkSigSchemeIds.add(APK_SIGNATURE_SCHEME_V2_ID); Loading @@ -74,6 +91,9 @@ public class ApkVerifier { if (result.containsErrors()) { return result; } } else { foundApkSigSchemeIds = Collections.emptySet(); } // Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N // ignore APK Signature Scheme v2 signatures and always attempt to verify JAR signatures. Loading @@ -86,7 +106,8 @@ public class ApkVerifier { zipSections, SUPPORTED_APK_SIG_SCHEME_NAMES, foundApkSigSchemeIds, minSdkVersion); minSdkVersion, maxSdkVersion); result.mergeFrom(v1Result); } if (result.containsErrors()) { Loading
tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java +73 −34 Original line number Diff line number Diff line Loading @@ -78,7 +78,14 @@ public abstract class V1SchemeVerifier { ApkUtils.ZipSections apkSections, Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion) throws IOException, ZipFormatException { int minSdkVersion, int maxSdkVersion) throws IOException, ZipFormatException { if (minSdkVersion > maxSdkVersion) { throw new IllegalArgumentException( "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion + ")"); } Result result = new Result(); // Parse the ZIP Central Directory and check that there are no entries with duplicate names. Loading @@ -97,6 +104,7 @@ public abstract class V1SchemeVerifier { supportedApkSigSchemeNames, foundApkSigSchemeIds, minSdkVersion, maxSdkVersion, result); return result; Loading Loading @@ -143,6 +151,7 @@ public abstract class V1SchemeVerifier { Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion, int maxSdkVersion, Result result) throws ZipFormatException, IOException { // Find JAR manifest and signature block files. Loading Loading @@ -243,7 +252,8 @@ public abstract class V1SchemeVerifier { // signature file .SF. Any error encountered for any signer terminates verification, to // mimic Android's behavior. for (Signer signer : signers) { signer.verifySigBlockAgainstSigFile(apk, cdStartOffset, minSdkVersion); signer.verifySigBlockAgainstSigFile( apk, cdStartOffset, minSdkVersion, maxSdkVersion); if (signer.getResult().containsErrors()) { result.signers.add(signer.getResult()); } Loading @@ -264,7 +274,8 @@ public abstract class V1SchemeVerifier { entryNameToManifestSection, supportedApkSigSchemeNames, foundApkSigSchemeIds, minSdkVersion); minSdkVersion, maxSdkVersion); if (signer.isIgnored()) { result.ignoredSigners.add(signer.getResult()); } else { Loading Loading @@ -393,7 +404,7 @@ public abstract class V1SchemeVerifier { @SuppressWarnings("restriction") public void verifySigBlockAgainstSigFile( DataSource apk, long cdStartOffset, int minSdkVersion) DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion) throws IOException, ZipFormatException { byte[] sigBlockBytes = LocalFileHeader.getUncompressedData( Loading Loading @@ -433,7 +444,8 @@ public abstract class V1SchemeVerifier { String signatureAlgorithmOid = unverifiedSignerInfo .getDigestEncryptionAlgorithmId().getOID().toString(); InclusiveIntRange desiredApiLevels = InclusiveIntRange.from(minSdkVersion); InclusiveIntRange desiredApiLevels = InclusiveIntRange.fromTo(minSdkVersion, maxSdkVersion); List<InclusiveIntRange> apiLevelsWhereDigestAndSigAlgorithmSupported = getSigAlgSupportedApiLevels(digestAlgorithmOid, signatureAlgorithmOid); List<InclusiveIntRange> apiLevelsWhereDigestAlgorithmNotSupported = Loading Loading @@ -843,7 +855,8 @@ public abstract class V1SchemeVerifier { Map<String, ManifestParser.Section> entryNameToManifestSection, Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { // Inspect the main section of the .SF file. ManifestParser sf = new ManifestParser(mSigFileBytes); ManifestParser.Section sfMainSection = sf.readSection(); Loading @@ -854,11 +867,17 @@ public abstract class V1SchemeVerifier { setIgnored(); return; } if (maxSdkVersion >= AndroidSdkVersion.N) { // Android N and newer rejects APKs whose .SF file says they were supposed to be // signed with APK Signature Scheme v2 (or newer) and yet no such signature was // found. checkForStrippedApkSignatures( sfMainSection, supportedApkSigSchemeNames, foundApkSigSchemeIds); if (mResult.containsErrors()) { return; } } boolean createdBySigntool = false; String createdBy = sfMainSection.getAttributeValue("Created-By"); Loading @@ -867,10 +886,18 @@ public abstract class V1SchemeVerifier { } boolean manifestDigestVerified = verifyManifestDigest( sfMainSection, createdBySigntool, manifestBytes, minSdkVersion); sfMainSection, createdBySigntool, manifestBytes, minSdkVersion, maxSdkVersion); if (!createdBySigntool) { verifyManifestMainSectionDigest( sfMainSection, manifestMainSection, manifestBytes, minSdkVersion); sfMainSection, manifestMainSection, manifestBytes, minSdkVersion, maxSdkVersion); } if (mResult.containsErrors()) { return; Loading Loading @@ -922,7 +949,8 @@ public abstract class V1SchemeVerifier { createdBySigntool, manifestSection, manifestBytes, minSdkVersion); minSdkVersion, maxSdkVersion); } mSigFileEntryNames = sfEntryNames; } Loading @@ -936,12 +964,14 @@ public abstract class V1SchemeVerifier { ManifestParser.Section sfMainSection, boolean createdBySigntool, byte[] manifestBytes, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { Collection<NamedDigest> expectedDigests = getDigestsToVerify( sfMainSection, ((createdBySigntool) ? "-Digest" : "-Digest-Manifest"), minSdkVersion); minSdkVersion, maxSdkVersion); boolean digestFound = !expectedDigests.isEmpty(); if (!digestFound) { mResult.addWarning( Loading Loading @@ -977,10 +1007,14 @@ public abstract class V1SchemeVerifier { ManifestParser.Section sfMainSection, ManifestParser.Section manifestMainSection, byte[] manifestBytes, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { Collection<NamedDigest> expectedDigests = getDigestsToVerify( sfMainSection, "-Digest-Manifest-Main-Attributes", minSdkVersion); sfMainSection, "-Digest-Manifest-Main-Attributes", minSdkVersion, maxSdkVersion); if (expectedDigests.isEmpty()) { return; } Loading Loading @@ -1014,10 +1048,12 @@ public abstract class V1SchemeVerifier { boolean createdBySigntool, ManifestParser.Section manifestIndividualSection, byte[] manifestBytes, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { String entryName = sfIndividualSection.getName(); Collection<NamedDigest> expectedDigests = getDigestsToVerify(sfIndividualSection, "-Digest", minSdkVersion); getDigestsToVerify( sfIndividualSection, "-Digest", minSdkVersion, maxSdkVersion); if (expectedDigests.isEmpty()) { mResult.addError( Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE, Loading Loading @@ -1124,7 +1160,8 @@ public abstract class V1SchemeVerifier { private static Collection<NamedDigest> getDigestsToVerify( ManifestParser.Section section, String digestAttrSuffix, int minSdkVersion) { int minSdkVersion, int maxSdkVersion) { Decoder base64Decoder = Base64.getDecoder(); List<NamedDigest> result = new ArrayList<>(1); if (minSdkVersion < AndroidSdkVersion.JELLY_BEAN_MR2) { Loading Loading @@ -1163,7 +1200,8 @@ public abstract class V1SchemeVerifier { } } // JB MR2 and newer, Android platform picks the strongest algorithm out of: if (maxSdkVersion >= AndroidSdkVersion.JELLY_BEAN_MR2) { // On JB MR2 and newer, Android platform picks the strongest algorithm out of: // SHA-512, SHA-384, SHA-256, SHA-1. for (String alg : JB_MR2_AND_NEWER_DIGEST_ALGS) { String attrName = getJarDigestAttributeName(alg, digestAttrSuffix); Loading @@ -1179,6 +1217,7 @@ public abstract class V1SchemeVerifier { } break; } } return result; } Loading