test/jdk/sun/security/tools/jarsigner/InsufficientSectionDelimiter.java
changeset 57488 94691d8e746f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/tools/jarsigner/InsufficientSectionDelimiter.java	Thu Jul 18 08:53:06 2019 +0800
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.stream.Stream;
+import java.util.jar.Attributes.Name;
+import java.util.jar.Manifest;
+import jdk.test.lib.util.JarUtils;
+import jdk.test.lib.SecurityTools;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * @test
+ * @bug 8217375
+ * @library /test/lib
+ * @run testng InsufficientSectionDelimiter
+ * @summary Checks some cases signing a jar the manifest of which has no or
+ * only one line break at the end and no proper delimiting blank line does not
+ * result in an invalid signed jar without jarsigner noticing and failing.
+ *
+ * <p>See also<ul>
+ * <li>{@link PreserveRawManifestEntryAndDigest} with an update of a signed
+ * jar with a different signer whereas this test just signs with one signer
+ * </li>
+ * <li>{@link WasSignedByOtherSigner} for a test that detects if
+ * {@code wasSigned} in {@link jdk.security.jarsigner.JarSigner#sign0} was set
+ * correctly determining whether or not to re-write the manifest, and</li>
+ * <li>{@code diffend.sh} for another similar test</li></ul>
+ */
+public class InsufficientSectionDelimiter {
+
+    static final String KEYSTORE_FILENAME = "test.jks";
+
+    @BeforeTest
+    public void prepareCertificate() throws Exception {
+        SecurityTools.keytool("-genkeypair -keyalg EC -keystore "
+                + KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
+                + " -alias a -dname CN=A").shouldHaveExitValue(0);
+    }
+
+    @BeforeTest
+    public void prepareFakeSfFile() throws IOException {
+        new File("META-INF").mkdir();
+        Files.write(Path.of("META-INF/.SF"), (
+                Name.SIGNATURE_VERSION + ": 1.0\r\n" +
+                "-Digest-Manifest: \r\n\r\n").getBytes(UTF_8));
+    }
+
+    @DataProvider(name = "parameters")
+    public static Object[][] parameters() {
+        return new String[][] { { "" }, { "\n" }, { "\r" }, { "\r\n" } };
+    }
+
+    @Factory(dataProvider = "parameters")
+    public static Object[] createTests(String lineBreak) {
+        return new Object[] { new InsufficientSectionDelimiter(lineBreak) };
+    }
+
+    final String lineBreak;
+    final String jarFilenameSuffix;
+
+    InsufficientSectionDelimiter(String lineBreak) {
+        this.lineBreak = lineBreak;
+        jarFilenameSuffix = Utils.escapeStringWithNumbers(lineBreak);
+    }
+
+    @BeforeMethod
+    public void verbose() {
+        System.out.println("lineBreak = "
+                + Utils.escapeStringWithNumbers(lineBreak));
+    }
+
+    void test(String jarFilenamePrefix, String... files) throws Exception {
+        String jarFilename = jarFilenamePrefix + jarFilenameSuffix + ".jar";
+        JarUtils.createJarFile(Path.of(jarFilename), new Manifest() {
+            @Override public void write(OutputStream out) throws IOException {
+                out.write((Name.MANIFEST_VERSION + ": 1.0" +
+                        lineBreak).getBytes(UTF_8));
+            }
+        }, Path.of("."), Stream.of(files).map(Path::of).toArray(Path[]::new)
+        );
+        Utils.echoManifest(Utils.readJarManifestBytes(
+                jarFilename), "unsigned jar");
+        try {
+            SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
+                    " -storepass changeit -verbose -debug " + jarFilename +
+                    " a").shouldHaveExitValue(0);
+            Utils.echoManifest(Utils.readJarManifestBytes(
+                    jarFilename), "signed jar");
+        } catch (Exception e) {
+            if (lineBreak.isEmpty()) {
+                return; // invalid manifest without trailing line break
+            }
+            throw e;
+        }
+
+        // remove META-INF/.SF from signed jar which would not validate
+        // (not added in all the test cases)
+        JarUtils.updateJar(jarFilename, "verify-" + jarFilename,
+                Map.of("META-INF/.SF", false));
+        SecurityTools.jarsigner("-verify -strict -keystore " +
+                KEYSTORE_FILENAME + " -storepass changeit -debug -verbose " +
+                "verify-" + jarFilename + " a").shouldHaveExitValue(0);
+    }
+
+    /**
+     * Test that signing a jar which has never been signed yet and contains
+     * no signature related files with a manifest that ends immediately after
+     * the last main attributes value byte or only one line break and no blank
+     * line produces a valid signed jar or an error if the manifest ends
+     * without line break.
+     */
+    @Test
+    public void testOnlyMainAttrs() throws Exception {
+        test("testOnlyMainAttrs");
+    }
+
+    /**
+     * Test that signing a jar with a manifest that ends immediately after
+     * the last main attributes value byte or with too few line break
+     * characters to properly delimit an individual section and has a fake
+     * signing related file to trigger a signature update or more specifically
+     * wasSigned in JarSigner.sign0 to become true produces a valid signed jar
+     * or an error if the manifest ends without line break.
+     * <p>
+     * Only one line break and hence no blank line ('\r', '\n', or '\r\n')
+     * after last main attributes value byte is too little to delimit an
+     * individual section to hold a file's digest but acceptable if no
+     * individual section has to be added because no contained file has to be
+     * signed as is the case in this test.
+     *
+     * @see #testMainAttrsWasSignedAddFile
+     */
+    @Test
+    public void testMainAttrsWasSigned() throws Exception {
+        test("testMainAttrsWasSigned", "META-INF/.SF");
+    }
+
+    /**
+     * Test that signing a jar with a manifest that ends immediately after
+     * the last main attributes value byte or with too few line break
+     * characters to properly delimit an individual section and has a fake
+     * signing related file to trigger a signature update or more specifically
+     * wasSigned in JarSigner.sign0 to become true produces no invalid signed
+     * jar or an error if the manifest ends without line break.
+     * <p>
+     * Only one line break and hence no blank line ('\r', '\n', or '\r\n')
+     * after the last main attributes value byte is too little to delimit an
+     * individual section which would be required here to save the digest of a
+     * contained file to be signed.
+     * <p>
+     * Changing the delimiters after the main attributes changes the main
+     * attributes digest but
+     * {@link SignatureFileVerifier#verifyManifestMainAttrs} and
+     * {@link ManifestDigester#digestWorkaround} work around it.
+     */
+    @Test
+    public void testMainAttrsWasSignedAddFile() throws Exception {
+        Files.write(Path.of("test.txt"), "test.txt".getBytes(UTF_8));
+        test("testMainAttrsWasSignedAddFile", "META-INF/.SF", "test.txt");
+    }
+
+}