Merge "maxSdkVersion can be specified for APK verification."
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
index 9c58085..c3999b5 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
@@ -56,23 +56,43 @@
* @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);
- try {
- V2SchemeVerifier.Result v2Result = V2SchemeVerifier.verify(apk, zipSections);
- foundApkSigSchemeIds.add(APK_SIGNATURE_SCHEME_V2_ID);
- result.mergeFrom(v2Result);
- } catch (V2SchemeVerifier.SignatureNotFoundException ignored) {}
- if (result.containsErrors()) {
- return result;
+
+ // 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);
+ result.mergeFrom(v2Result);
+ } catch (V2SchemeVerifier.SignatureNotFoundException ignored) {}
+ if (result.containsErrors()) {
+ return result;
+ }
+ } else {
+ foundApkSigSchemeIds = Collections.emptySet();
}
// Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N
@@ -86,7 +106,8 @@
zipSections,
SUPPORTED_APK_SIG_SCHEME_NAMES,
foundApkSigSchemeIds,
- minSdkVersion);
+ minSdkVersion,
+ maxSdkVersion);
result.mergeFrom(v1Result);
}
if (result.containsErrors()) {
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java
index 91aa62e..60a47b2 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java
@@ -78,7 +78,14 @@
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.
@@ -97,6 +104,7 @@
supportedApkSigSchemeNames,
foundApkSigSchemeIds,
minSdkVersion,
+ maxSdkVersion,
result);
return result;
@@ -143,6 +151,7 @@
Map<Integer, String> supportedApkSigSchemeNames,
Set<Integer> foundApkSigSchemeIds,
int minSdkVersion,
+ int maxSdkVersion,
Result result) throws ZipFormatException, IOException {
// Find JAR manifest and signature block files.
@@ -243,7 +252,8 @@
// 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());
}
@@ -264,7 +274,8 @@
entryNameToManifestSection,
supportedApkSigSchemeNames,
foundApkSigSchemeIds,
- minSdkVersion);
+ minSdkVersion,
+ maxSdkVersion);
if (signer.isIgnored()) {
result.ignoredSigners.add(signer.getResult());
} else {
@@ -393,7 +404,7 @@
@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(
@@ -433,7 +444,8 @@
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 =
@@ -843,7 +855,8 @@
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();
@@ -854,10 +867,16 @@
setIgnored();
return;
}
- checkForStrippedApkSignatures(
- sfMainSection, supportedApkSigSchemeNames, foundApkSigSchemeIds);
- if (mResult.containsErrors()) {
- 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;
@@ -867,10 +886,18 @@
}
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;
@@ -922,7 +949,8 @@
createdBySigntool,
manifestSection,
manifestBytes,
- minSdkVersion);
+ minSdkVersion,
+ maxSdkVersion);
}
mSigFileEntryNames = sfEntryNames;
}
@@ -936,12 +964,14 @@
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(
@@ -977,10 +1007,14 @@
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;
}
@@ -1014,10 +1048,12 @@
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,
@@ -1124,7 +1160,8 @@
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) {
@@ -1163,21 +1200,23 @@
}
}
- // 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);
- String digestBase64 = section.getAttributeValue(attrName);
- if (digestBase64 == null) {
- // Attribute not found
- continue;
+ 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);
+ String digestBase64 = section.getAttributeValue(attrName);
+ if (digestBase64 == null) {
+ // Attribute not found
+ continue;
+ }
+ byte[] digest = base64Decoder.decode(digestBase64);
+ byte[] digestInResult = getDigest(result, alg);
+ if ((digestInResult == null) || (!Arrays.equals(digestInResult, digest))) {
+ result.add(new NamedDigest(alg, digest));
+ }
+ break;
}
- byte[] digest = base64Decoder.decode(digestBase64);
- byte[] digestInResult = getDigest(result, alg);
- if ((digestInResult == null) || (!Arrays.equals(digestInResult, digest))) {
- result.add(new NamedDigest(alg, digest));
- }
- break;
}
return result;