Path: blob/master/test/jdk/sun/security/tools/jarsigner/FindHeaderEndVsManifestDigesterFindFirstSection.java
41152 views
/*1* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.io.ByteArrayOutputStream;24import java.security.MessageDigest;25import java.util.ArrayList;26import java.util.List;27import sun.security.util.ManifestDigester;2829import org.testng.annotations.Test;30import org.testng.annotations.DataProvider;31import org.testng.annotations.Factory;32import org.testng.annotations.BeforeClass;33import org.testng.annotations.BeforeMethod;3435import static java.nio.charset.StandardCharsets.UTF_8;36import static org.testng.Assert.*;3738/**39* @test40* @bug 821737541* @modules java.base/sun.security.util42* @run testng FindHeaderEndVsManifestDigesterFindFirstSection43* @summary Checks that {@link JarSigner#findHeaderEnd} (moved to now44* {@link #findHeaderEnd} in this test) can be replaced with45* {@link ManifestDigester#findSection}46* (first invocation will identify main attributes)47* without making a difference.48*/49/*50* Note to future maintainer:51* While it might look at first glance like this test ensures backwards-52* compatibility between JarSigner.findHeaderEnd and53* ManifestDigester.findSection's first invocation that find the main54* attributes section, at the time of that change, this test continues to55* verify main attributes digestion now with ManifestDigester.findSection as56* opposed to previous implementation in JarSigner.findHeaderEnd.57* Before completely removing this test, make sure that main attributes58* digestion is covered appropriately with tests. After JarSigner.findHeaderEnd59* has been removed digests should still continue to match.60*61* See also62* - jdk/test/jdk/sun/security/tools/jarsigner/PreserveRawManifestEntryAndDigest.java63* for some end-to-end tests utilizing the jarsigner tool,64* - jdk/test/jdk/sun/security/util/ManifestDigester/FindSection.java and65* - jdk/test/jdk/sun/security/util/ManifestDigester/DigestInput.java66* for much more detailed tests at api level67*68* Both test mentioned above, however, originally were created when removing69* confusion of "Manifest-Main-Attributes" individual section with actual main70* attributes whereas the test here is about changes related to raw manifest71* reproduction and in the end test pretty much the same behavior.72*/73public class FindHeaderEndVsManifestDigesterFindFirstSection {7475static final boolean FIXED_8217375 = true; // FIXME7677/**78* from former {@link JarSigner#findHeaderEnd}, subject to verification if79* it can be replaced with {@link ManifestDigester#findSection}80*/81@SuppressWarnings("fallthrough")82private int findHeaderEnd(byte[] bs) {83// Initial state true to deal with empty header84boolean newline = true; // just met a newline85int len = bs.length;86for (int i = 0; i < len; i++) {87switch (bs[i]) {88case '\r':89if (i < len - 1 && bs[i + 1] == '\n') i++;90// fallthrough91case '\n':92if (newline) return i + 1; //+1 to get length93newline = true;94break;95default:96newline = false;97}98}99// If header end is not found, it means the MANIFEST.MF has only100// the main attributes section and it does not end with 2 newlines.101// Returns the whole length so that it can be completely replaced.102return len;103}104105@DataProvider(name = "parameters")106public static Object[][] parameters() {107List<Object[]> tests = new ArrayList<>();108for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {109if ("\r".equals(lineBreak) && !FIXED_8217375) continue;110for (int numLBs = 0; numLBs <= 3; numLBs++) {111for (String addSection : new String[] { null, "Ignore" }) {112tests.add(new Object[] { lineBreak, numLBs, addSection });113}114}115}116return tests.toArray(new Object[tests.size()][]);117}118119@Factory(dataProvider = "parameters")120public static Object[] createTests(String lineBreak, int numLineBreaks,121String individualSectionName) {122return new Object[]{new FindHeaderEndVsManifestDigesterFindFirstSection(123lineBreak, numLineBreaks, individualSectionName124)};125}126127final String lineBreak;128final int numLineBreaks; // number of line breaks after main attributes129final String individualSectionName; // null means only main attributes130final byte[] rawBytes;131132FindHeaderEndVsManifestDigesterFindFirstSection(String lineBreak,133int numLineBreaks, String individualSectionName) {134this.lineBreak = lineBreak;135this.numLineBreaks = numLineBreaks;136this.individualSectionName = individualSectionName;137138rawBytes = (139"oldStyle: trailing space " + lineBreak +140"newStyle: no trailing space" + lineBreak.repeat(numLineBreaks) +141// numLineBreaks < 2 will not properly delimit individual section142// but it does not hurt to test that anyway143(individualSectionName != null ?144"Name: " + individualSectionName + lineBreak +145"Ignore: nothing here" + lineBreak +146lineBreak147: "")148).getBytes(UTF_8);149}150151@BeforeMethod152public void verbose() {153System.out.println("lineBreak = " + stringToIntList(lineBreak));154System.out.println("numLineBreaks = " + numLineBreaks);155System.out.println("individualSectionName = " + individualSectionName);156}157158@FunctionalInterface159interface Callable {160void call() throws Exception;161}162163void catchNoLineBreakAfterMainAttributes(Callable test) throws Exception {164// manifests cannot be parsed and digested if the main attributes do165// not end in a blank line (double line break) or one line break166// immediately before eof.167boolean failureExpected = numLineBreaks == 0168&& individualSectionName == null;169try {170test.call();171if (failureExpected) fail("expected an exception");172} catch (NullPointerException | IllegalStateException e) {173if (!failureExpected) fail("unexpected " + e.getMessage(), e);174}175}176177/**178* Checks that the beginning of the manifest until position<ol>179* <li>{@code Jarsigner.findHeaderEnd} in the previous version180* and</li>181* <li>{@code ManifestDigester.getMainAttsEntry().sections[0].182* lengthWithBlankLine} in the new version</li>183* </ol>produce the same offset (TODO: or the same error).184* The beginning of the manifest until that offset (range185* <pre>0 .. (offset - 1)</pre>) will be reproduced if the manifest has186* not changed.187* <p>188* Getting {@code startOfNext} of {@link ManifestDigester#findSection}'s189* first invokation returned {@link ManifestDigester.Position} which190* identifies the end offset of the main attributes is difficulted by191* {@link ManifestDigester#findSection} being private and therefore not192* directly accessible.193*/194@Test195public void startOfNextLengthWithBlankLine() throws Exception {196catchNoLineBreakAfterMainAttributes(() ->197assertEquals(lengthWithBlankLine(), findHeaderEnd(rawBytes))198);199}200201/**202* Due to its private visibility,203* {@link ManifestDigester.Section#lengthWithBlankLine} is not directly204* accessible. However, calling {@link ManifestDigester.Entry#digest}205* reveals {@code lengthWithBlankLine} as third parameter in206* <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>207* on line ManifestDigester.java:212.208* <p>209* This value is the same as {@code startOfNext} of210* {@link ManifestDigester#findSection}'s first invocation returned211* {@link ManifestDigester.Position} identifying the end offset of the212* main attributes because<ol>213* <li>the end offset of the main attributes is assigned to214* {@code startOfNext} in215* <pre>pos.startOfNext = i+1;</pre> in ManifestDigester.java:98</li>216* <li>which is then passed on as the third parameter to the constructor217* of a new {@link ManifestDigester.Section#Section} by218* <pre>new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)));</pre>219* in in ManifestDigester.java:128</li>220* <li>where it is assigned to221* {@link ManifestDigester.Section#lengthWithBlankLine} by222* <pre>this.lengthWithBlankLine = lengthWithBlankLine;</pre>223* in ManifestDigester.java:241</li>224* <li>from where it is picked up by {@link ManifestDigester.Entry#digest}225* in226* <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>227* in ManifestDigester.java:212</li>228* </ol>229* all of which without any modification.230*/231int lengthWithBlankLine() {232int[] lengthWithBlankLine = new int[] { 0 };233new ManifestDigester(rawBytes).get(ManifestDigester.MF_MAIN_ATTRS,234false).digest(new MessageDigest("lengthWithBlankLine") {235@Override protected void engineReset() {236lengthWithBlankLine[0] = 0;237}238@Override protected void engineUpdate(byte b) {239lengthWithBlankLine[0]++;240}241@Override protected void engineUpdate(byte[] b, int o, int l) {242lengthWithBlankLine[0] += l;243}244@Override protected byte[] engineDigest() {245return null;246}247});248return lengthWithBlankLine[0];249}250251/**252* Checks that the replacement of {@link JarSigner#findHeaderEnd} is253* actually used to reproduce manifest main attributes.254* <p>255* {@link #startOfNextLengthWithBlankLine} demonstrates that256* {@link JarSigner#findHeaderEnd} has been replaced successfully with257* {@link ManifestDigester#findSection} but does not also show that the258* main attributes are reproduced with the same offset as before.259* {@link #startOfNextLengthWithBlankLine} uses260* {@link ManifestDigester.Entry#digest} to demonstrate an equal offset261* calculated but {@link ManifestDigester.Entry#digest} is not necessarily262* the same as reproducing, especially when considering263* {@link ManifestDigester.Entry#oldStyle}.264*/265@Test(enabled = FIXED_8217375)266public void reproduceMainAttributes() throws Exception {267catchNoLineBreakAfterMainAttributes(() -> {268ByteArrayOutputStream buf = new ByteArrayOutputStream();269ManifestDigester md = new ManifestDigester(rawBytes);270// without 8217375 fixed the following line will not even compile271// so just remove it and skip the test for regression272md.getMainAttsEntry().reproduceRaw(buf); // FIXME273274assertEquals(buf.size(), findHeaderEnd(rawBytes));275});276}277278static List<Integer> stringToIntList(String string) {279byte[] bytes = string.getBytes(UTF_8);280List<Integer> list = new ArrayList<>();281for (int i = 0; i < bytes.length; i++) {282list.add((int) bytes[i]);283}284return list;285}286287}288289290