CR, LF, and NUL are not permitted in ZIP entry names
am: 27fbfa6017

Change-Id: I02d63131530f812053cd5fdd8e24a3cc13250964
diff --git a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
index af98007..0509d73 100644
--- a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
@@ -362,6 +362,7 @@
         SortedMap<String, byte[]> invidualSectionsContents = new TreeMap<>();
         String entryDigestAttributeName = getEntryDigestAttributeName(jarEntryDigestAlgorithm);
         for (String entryName : sortedEntryNames) {
+            checkEntryNameValid(entryName);
             byte[] entryDigest = jarEntryDigests.get(entryName);
             Attributes entryAttrs = new Attributes();
             entryAttrs.putValue(
@@ -386,6 +387,22 @@
         return result;
     }
 
+    private static void checkEntryNameValid(String name) throws ApkFormatException {
+        // JAR signing spec says CR, LF, and NUL are not permitted in entry names
+        // CR or LF in entry names will result in malformed MANIFEST.MF and .SF files because there
+        // is no way to escape characters in MANIFEST.MF and .SF files. NUL can, presumably, cause
+        // issues when parsing using C and C++ like languages.
+        for (char c : name.toCharArray()) {
+            if ((c == '\r') || (c == '\n') || (c == 0)) {
+                throw new ApkFormatException(
+                        String.format(
+                                "Unsupported character 0x%1$02x in ZIP entry name \"%2$s\"",
+                                (int) c,
+                                name));
+            }
+        }
+    }
+
     public static class OutputManifestFile {
         public byte[] contents;
         public SortedMap<String, byte[]> individualSectionsContents;
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index bcd0fe1..fe2b8cf 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -313,6 +313,31 @@
                 verifyForMinSdkVersion(out, 17), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG);
     }
 
+    @Test
+    public void testV1SigningRejectsInvalidZipEntryNames() throws Exception {
+        // ZIP/JAR entry name cannot contain CR, LF, or NUL characters when the APK is being
+        // JAR-signed.
+        List<ApkSigner.SignerConfig> signers =
+                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+        try {
+            sign("v1-only-with-cr-in-entry-name.apk",
+                    new ApkSigner.Builder(signers).setV1SigningEnabled(true));
+            fail();
+        } catch (ApkFormatException expected) {}
+
+        try {
+            sign("v1-only-with-lf-in-entry-name.apk",
+                    new ApkSigner.Builder(signers).setV1SigningEnabled(true));
+            fail();
+        } catch (ApkFormatException expected) {}
+
+        try {
+            sign("v1-only-with-nul-in-entry-name.apk",
+                    new ApkSigner.Builder(signers).setV1SigningEnabled(true));
+            fail();
+        } catch (ApkFormatException expected) {}
+    }
+
     /**
      * Asserts that signing the specified golden input file using the provided signing
      * configuration produces output identical to the specified golden output file.
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 0c1cc8b..6059ffc 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -629,6 +629,17 @@
                         "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 17));
     }
 
+    @Test
+    public void testV1WithUnsupportedCharacterInZipEntryName() throws Exception {
+        // Android Package Manager does not support ZIP entry names containing CR or LF
+        assertVerificationFailure(
+                verify("v1-only-with-cr-in-entry-name.apk"),
+                Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION);
+        assertVerificationFailure(
+                verify("v1-only-with-lf-in-entry-name.apk"),
+                Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION);
+    }
+
     private ApkVerifier.Result verify(String apkFilenameInResources)
             throws IOException, ApkFormatException, NoSuchAlgorithmException {
         return verify(apkFilenameInResources, null, null);
diff --git a/src/test/resources/com/android/apksig/v1-only-with-cr-in-entry-name.apk b/src/test/resources/com/android/apksig/v1-only-with-cr-in-entry-name.apk
new file mode 100644
index 0000000..84828b0
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-cr-in-entry-name.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-lf-in-entry-name.apk b/src/test/resources/com/android/apksig/v1-only-with-lf-in-entry-name.apk
new file mode 100644
index 0000000..7d341da
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-lf-in-entry-name.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-nul-in-entry-name.apk b/src/test/resources/com/android/apksig/v1-only-with-nul-in-entry-name.apk
new file mode 100644
index 0000000..2bcdec1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-nul-in-entry-name.apk
Binary files differ