| /* | |
| * 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.ByteArrayOutputStream; | |
| import java.io.IOException; | |
| import java.security.MessageDigest; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.List; | |
| import java.util.jar.Attributes.Name; | |
| import java.util.stream.Collectors; | |
| import sun.security.util.ManifestDigester; | |
| import org.testng.annotations.DataProvider; | |
| import org.testng.annotations.Factory; | |
| import org.testng.annotations.BeforeMethod; | |
| import org.testng.annotations.AfterTest; | |
| import org.testng.annotations.Test; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import static org.testng.Assert.*; | |
| /** | |
| * @test | |
| * @bug 8217375 | |
| * @modules java.base/sun.security.util | |
| * @compile ../../tools/jarsigner/Utils.java | |
| * @run testng DigestInput | |
| * @summary Checks that the manifest main attributes and entry digests are the | |
| * same as before resolution of bug 8217375 which means they treat some white | |
| * space different for oldStyle or digestWorkaround except for the blank line | |
| * at the end of the manifest file for digestWorkaround. | |
| */ | |
| public class DigestInput { | |
| /** | |
| * Filters some test cases for calibrating expected digests with previous | |
| * implementation. TODO: Delete this after calibrating with old sources. | |
| */ | |
| static final boolean FIXED_8217375 = true; // FIXME | |
| /** | |
| * {@link ManifestDigester.Entry#digestWorkaround} should not feed the | |
| * trailing blank line into the digester. Before resolution of 8217375 it | |
| * fed the trailing blank line into the digest if the second line break | |
| * was at the end of the file due to <pre> | |
| * if (allBlank || (i == len-1)) { | |
| * if (i == len-1) | |
| * pos.endOfSection = i; | |
| * else | |
| * pos.endOfSection = last; | |
| * </pre> in {@link ManifestDigester#findSection}. In that case at the end | |
| * of the manifest file, {@link ManifestDigester.Entry#digestWorkaround} | |
| * would have produced the same digest as | |
| * {@link ManifestDigester.Entry#digest} which was wrong and without effect | |
| * at best. | |
| * <p> | |
| * Once this fix is accepted, this flag can be removed along with | |
| * {@link #assertDigestEqualsCatchWorkaroundBroken}. | |
| */ | |
| static final boolean FIXED_8217375_EOF_ENDOFSECTION = FIXED_8217375; | |
| static final String SECTION_NAME = "some individual section name"; | |
| @DataProvider(name = "parameters") | |
| public static Object[][] parameters() { | |
| List<Object[]> tests = new ArrayList<>(); | |
| for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) { | |
| if ("\r".equals(lineBreak) && !FIXED_8217375) continue; | |
| for (int addLB = 0; addLB <= 4; addLB++) { | |
| for (int numSecs = 0; numSecs <= 4; numSecs++) { | |
| for (boolean otherSec : new Boolean[] { false, true }) { | |
| for (boolean oldStyle : new Boolean[] { false, true }) { | |
| for (boolean workaround : | |
| new Boolean[] { false, true }) { | |
| tests.add(new Object[] { | |
| lineBreak, addLB, numSecs, otherSec, | |
| oldStyle, workaround | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return tests.toArray(new Object[tests.size()][]); | |
| } | |
| @Factory(dataProvider = "parameters") | |
| public static Object[] createTests( | |
| String lineBreak, int additionalLineBreaks, | |
| int numberOfSections, boolean hasOtherSection, | |
| boolean oldStyle, boolean digestWorkaround) { | |
| return new Object[] { new DigestInput(lineBreak, | |
| additionalLineBreaks, numberOfSections, hasOtherSection, | |
| oldStyle, digestWorkaround) | |
| }; | |
| } | |
| final String lineBreak; | |
| final int additionalLineBreaks; // number of blank lines delimiting section | |
| final int numberOfSections; | |
| final boolean hasOtherSection; | |
| final boolean oldStyle; | |
| final boolean digestWorkaround; | |
| public DigestInput( | |
| String lineBreak, int additionalLineBreaks, | |
| int numberOfSections, boolean hasOtherSection, | |
| boolean oldStyle, boolean digestWorkaround) { | |
| this.lineBreak = lineBreak; | |
| this.additionalLineBreaks = additionalLineBreaks; | |
| this.numberOfSections = numberOfSections; | |
| this.hasOtherSection = hasOtherSection; | |
| this.oldStyle = oldStyle; | |
| this.digestWorkaround = digestWorkaround; | |
| } | |
| @BeforeMethod | |
| public void verbose() { | |
| System.out.println("-".repeat(72)); | |
| System.out.println("lineBreak = " + | |
| Utils.escapeStringWithNumbers(lineBreak)); | |
| System.out.println("additionalLineBreaks = " + additionalLineBreaks); | |
| System.out.println("numberOfSections = " + numberOfSections); | |
| System.out.println("hasOtherSection = " + hasOtherSection); | |
| System.out.println("oldStyle = " + oldStyle); | |
| System.out.println("digestWorkaround = " + digestWorkaround); | |
| System.out.println("-".repeat(72)); | |
| } | |
| byte[] rawManifestBytes() { | |
| return ( | |
| Name.MANIFEST_VERSION + ": 1.0" + lineBreak + | |
| "OldStyle0: no trailing space" + lineBreak + | |
| "OldStyle1: trailing space " + lineBreak + | |
| "OldStyle2: two trailing spaces " + lineBreak + | |
| lineBreak.repeat(additionalLineBreaks) + | |
| ( | |
| "Name: " + SECTION_NAME + lineBreak + | |
| "OldStyle0: no trailing space" + lineBreak + | |
| "OldStyle1: trailing space " + lineBreak + | |
| "OldStyle2: two trailing spaces " + lineBreak + | |
| lineBreak.repeat(additionalLineBreaks) | |
| ).repeat(numberOfSections) + | |
| (hasOtherSection ? | |
| "Name: unrelated trailing section" + lineBreak + | |
| "OldStyle0: no trailing space" + lineBreak + | |
| "OldStyle1: trailing space " + lineBreak + | |
| "OldStyle2: two trailing spaces " + lineBreak + | |
| lineBreak.repeat(additionalLineBreaks) | |
| : "") | |
| ).getBytes(UTF_8); | |
| } | |
| byte[] expectedMainAttrsDigest(boolean digestWorkaround) { | |
| return ( | |
| Name.MANIFEST_VERSION + ": 1.0" + lineBreak + | |
| "OldStyle0: no trailing space" + lineBreak + | |
| "OldStyle1: trailing space" + | |
| (!oldStyle || !lineBreak.startsWith("\r") || digestWorkaround ? | |
| " " : "") + lineBreak + | |
| "OldStyle2: two trailing spaces " + | |
| (!oldStyle || !lineBreak.startsWith("\r") || digestWorkaround ? | |
| " " : "") + lineBreak + | |
| ( | |
| ( | |
| !digestWorkaround | |
| || ( | |
| additionalLineBreaks == 1 | |
| && numberOfSections == 0 | |
| && !hasOtherSection | |
| && ( | |
| digestWorkaround | |
| && !FIXED_8217375_EOF_ENDOFSECTION | |
| ) | |
| ) | |
| ) && ( | |
| additionalLineBreaks > 0 | |
| || numberOfSections > 0 | |
| || hasOtherSection | |
| ) | |
| ? lineBreak : "") | |
| ).getBytes(UTF_8); | |
| } | |
| byte[] expectedIndividualSectionDigest(boolean digestWorkaround) { | |
| if (numberOfSections == 0) return null; | |
| return ( | |
| ( | |
| "Name: " + SECTION_NAME + lineBreak + | |
| "OldStyle0: no trailing space" + lineBreak + | |
| "OldStyle1: trailing space" + | |
| (!oldStyle || !lineBreak.startsWith("\r") | |
| || digestWorkaround ? " " : "") + lineBreak + | |
| "OldStyle2: two trailing spaces " + | |
| (!oldStyle || !lineBreak.startsWith("\r") | |
| || digestWorkaround ? " " : "") + lineBreak + | |
| ( | |
| ( | |
| !digestWorkaround | |
| ) && ( | |
| additionalLineBreaks > 0 | |
| ) | |
| ? lineBreak : "") | |
| ).repeat(numberOfSections) + | |
| ( | |
| additionalLineBreaks == 1 | |
| && !hasOtherSection | |
| && digestWorkaround | |
| && !FIXED_8217375_EOF_ENDOFSECTION | |
| ? lineBreak : "") | |
| ).getBytes(UTF_8); | |
| } | |
| class EchoMessageDigest extends MessageDigest { | |
| ByteArrayOutputStream buf; | |
| EchoMessageDigest() { | |
| super("echo"); | |
| } | |
| @Override | |
| protected void engineReset() { | |
| buf = new ByteArrayOutputStream(); | |
| } | |
| @Override | |
| protected void engineUpdate(byte input) { | |
| buf.write(input); | |
| } | |
| @Override | |
| protected void engineUpdate(byte[] i, int o, int l) { | |
| buf.write(i, o, l); | |
| } | |
| @Override protected byte[] engineDigest() { | |
| return buf.toByteArray(); | |
| } | |
| } | |
| byte[] digestMainAttributes(byte[] mfBytes) throws Exception { | |
| Utils.echoManifest(mfBytes, "going to digest main attributes of"); | |
| ManifestDigester md = new ManifestDigester(mfBytes); | |
| ManifestDigester.Entry entry = | |
| md.get(ManifestDigester.MF_MAIN_ATTRS, oldStyle); | |
| MessageDigest digester = new EchoMessageDigest(); | |
| return digestWorkaround ? | |
| entry.digestWorkaround(digester) : entry.digest(digester); | |
| } | |
| byte[] digestIndividualSection(byte[] mfBytes) throws Exception { | |
| Utils.echoManifest(mfBytes, | |
| "going to digest section " + SECTION_NAME + " of"); | |
| ManifestDigester md = new ManifestDigester(mfBytes); | |
| ManifestDigester.Entry entry = md.get(SECTION_NAME, oldStyle); | |
| if (entry == null) { | |
| return null; | |
| } | |
| MessageDigest digester = new EchoMessageDigest(); | |
| return digestWorkaround ? | |
| entry.digestWorkaround(digester) : entry.digest(digester); | |
| } | |
| /** | |
| * Checks that the manifest main attributes digest is the same as before. | |
| */ | |
| @Test | |
| public void testMainAttributesDigest() throws Exception { | |
| byte[] mfRaw = rawManifestBytes(); | |
| byte[] digest = digestMainAttributes(mfRaw); | |
| byte[] expectedDigest = expectedMainAttrsDigest(digestWorkaround); | |
| // the individual section will be digested along with the main | |
| // attributes if not properly delimited with a blank line | |
| if (additionalLineBreaks == 0 | |
| && (numberOfSections > 0 || hasOtherSection)) { | |
| assertNotEquals(digest, expectedDigest); | |
| return; | |
| } | |
| byte[] expectedDigestNoWorkaround = expectedMainAttrsDigest(false); | |
| // assertDigestEquals(digest, expectedDigest); // FIXME | |
| assertDigestEqualsCatchWorkaroundBroken( | |
| digest, expectedDigest, expectedDigestNoWorkaround); | |
| } | |
| /** | |
| * Checks that an individual section digest is the same as before. | |
| */ | |
| @Test | |
| public void testIndividualSectionDigest() throws Exception { | |
| byte[] mfRaw = rawManifestBytes(); | |
| byte[] digest = digestIndividualSection(mfRaw); | |
| // no digest will be produced for an individual section that is not | |
| // properly section delimited with a blank line. | |
| byte[] expectedDigest = | |
| additionalLineBreaks == 0 ? null : | |
| expectedIndividualSectionDigest(digestWorkaround); | |
| byte[] expectedDigestNoWorkaround = | |
| additionalLineBreaks == 0 ? null : | |
| expectedIndividualSectionDigest(false); | |
| // assertDigestEquals(digest, expectedDigest); // FIXME | |
| assertDigestEqualsCatchWorkaroundBroken( | |
| digest, expectedDigest, expectedDigestNoWorkaround); | |
| } | |
| static int firstDiffPos = Integer.MAX_VALUE; | |
| /** | |
| * @see FIXED_8217375_EOF_ENDOFSECTION | |
| */ | |
| void assertDigestEqualsCatchWorkaroundBroken( | |
| byte[] actual, byte[] expected, byte[] expectedNoWorkaround) | |
| throws IOException { | |
| try { | |
| assertDigestEquals(actual, expected); | |
| } catch (AssertionError e) { | |
| if (digestWorkaround && FIXED_8217375_EOF_ENDOFSECTION && | |
| Arrays.equals(expected, expectedNoWorkaround)) { | |
| // if digests with and without workaround are the same anyway | |
| // the workaround has failed and could not have worked with | |
| // the same digest as produced without workaround before | |
| // which would not match either because equal. | |
| return; | |
| } | |
| fail("failed also without digestWorkaound", e); | |
| } | |
| } | |
| void assertDigestEquals(byte[] actual, byte[] expected) throws IOException { | |
| if (actual == null && expected == null) return; | |
| Utils.echoManifest(actual, "actual digest"); | |
| Utils.echoManifest(expected, "expected digest"); | |
| for (int i = 0; i < actual.length && i < expected.length; i++) { | |
| if (actual[i] != expected[i]) { | |
| firstDiffPos = Math.min(firstDiffPos, i); | |
| verbose(); | |
| fail("found first difference in current test" | |
| + " at position " + i); | |
| } | |
| } | |
| if (actual.length != expected.length) { | |
| int diffPos = Math.min(actual.length, expected.length); | |
| firstDiffPos = Math.min(firstDiffPos, diffPos); | |
| verbose(); | |
| fail("found first difference in current test" | |
| + " at position " + diffPos + " after one digest end"); | |
| } | |
| assertEquals(actual, expected); | |
| } | |
| @AfterTest | |
| public void reportFirstDiffPos() { | |
| System.err.println("found first difference in all tests" | |
| + " at position " + firstDiffPos); | |
| } | |
| } |