e41686b405
Co-authored-by: Michael Ernst <mernst@openjdk.org> Reviewed-by: lancea
289 lines
12 KiB
Java
289 lines
12 KiB
Java
/*
|
|
* 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.security.MessageDigest;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import sun.security.util.ManifestDigester;
|
|
|
|
import org.testng.annotations.Test;
|
|
import org.testng.annotations.DataProvider;
|
|
import org.testng.annotations.Factory;
|
|
import org.testng.annotations.BeforeClass;
|
|
import org.testng.annotations.BeforeMethod;
|
|
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
import static org.testng.Assert.*;
|
|
|
|
/**
|
|
* @test
|
|
* @bug 8217375
|
|
* @modules java.base/sun.security.util
|
|
* @run testng FindHeaderEndVsManifestDigesterFindFirstSection
|
|
* @summary Checks that {@link JarSigner#findHeaderEnd} (moved to now
|
|
* {@link #findHeaderEnd} in this test) can be replaced with
|
|
* {@link ManifestDigester#findSection}
|
|
* (first invocation will identify main attributes)
|
|
* without making a difference.
|
|
*/
|
|
/*
|
|
* Note to future maintainer:
|
|
* While it might look at first glance like this test ensures backwards-
|
|
* compatibility between JarSigner.findHeaderEnd and
|
|
* ManifestDigester.findSection's first invocation that find the main
|
|
* attributes section, at the time of that change, this test continues to
|
|
* verify main attributes digestion now with ManifestDigester.findSection as
|
|
* opposed to previous implementation in JarSigner.findHeaderEnd.
|
|
* Before completely removing this test, make sure that main attributes
|
|
* digestion is covered appropriately with tests. After JarSigner.findHeaderEnd
|
|
* has been removed digests should still continue to match.
|
|
*
|
|
* See also
|
|
* - jdk/test/jdk/sun/security/tools/jarsigner/PreserveRawManifestEntryAndDigest.java
|
|
* for some end-to-end tests utilizing the jarsigner tool,
|
|
* - jdk/test/jdk/sun/security/util/ManifestDigester/FindSection.java and
|
|
* - jdk/test/jdk/sun/security/util/ManifestDigester/DigestInput.java
|
|
* for much more detailed tests at api level
|
|
*
|
|
* Both test mentioned above, however, originally were created when removing
|
|
* confusion of "Manifest-Main-Attributes" individual section with actual main
|
|
* attributes whereas the test here is about changes related to raw manifest
|
|
* reproduction and in the end test pretty much the same behavior.
|
|
*/
|
|
public class FindHeaderEndVsManifestDigesterFindFirstSection {
|
|
|
|
static final boolean FIXED_8217375 = true; // FIXME
|
|
|
|
/**
|
|
* from former {@link JarSigner#findHeaderEnd}, subject to verification if
|
|
* it can be replaced with {@link ManifestDigester#findSection}
|
|
*/
|
|
@SuppressWarnings("fallthrough")
|
|
private int findHeaderEnd(byte[] bs) {
|
|
// Initial state true to deal with empty header
|
|
boolean newline = true; // just met a newline
|
|
int len = bs.length;
|
|
for (int i = 0; i < len; i++) {
|
|
switch (bs[i]) {
|
|
case '\r':
|
|
if (i < len - 1 && bs[i + 1] == '\n') i++;
|
|
// fallthrough
|
|
case '\n':
|
|
if (newline) return i + 1; //+1 to get length
|
|
newline = true;
|
|
break;
|
|
default:
|
|
newline = false;
|
|
}
|
|
}
|
|
// If header end is not found, it means the MANIFEST.MF has only
|
|
// the main attributes section and it does not end with 2 newlines.
|
|
// Returns the whole length so that it can be completely replaced.
|
|
return len;
|
|
}
|
|
|
|
@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 numLBs = 0; numLBs <= 3; numLBs++) {
|
|
for (String addSection : new String[] { null, "Ignore" }) {
|
|
tests.add(new Object[] { lineBreak, numLBs, addSection });
|
|
}
|
|
}
|
|
}
|
|
return tests.toArray(new Object[tests.size()][]);
|
|
}
|
|
|
|
@Factory(dataProvider = "parameters")
|
|
public static Object[] createTests(String lineBreak, int numLineBreaks,
|
|
String individualSectionName) {
|
|
return new Object[]{new FindHeaderEndVsManifestDigesterFindFirstSection(
|
|
lineBreak, numLineBreaks, individualSectionName
|
|
)};
|
|
}
|
|
|
|
final String lineBreak;
|
|
final int numLineBreaks; // number of line breaks after main attributes
|
|
final String individualSectionName; // null means only main attributes
|
|
final byte[] rawBytes;
|
|
|
|
FindHeaderEndVsManifestDigesterFindFirstSection(String lineBreak,
|
|
int numLineBreaks, String individualSectionName) {
|
|
this.lineBreak = lineBreak;
|
|
this.numLineBreaks = numLineBreaks;
|
|
this.individualSectionName = individualSectionName;
|
|
|
|
rawBytes = (
|
|
"oldStyle: trailing space " + lineBreak +
|
|
"newStyle: no trailing space" + lineBreak.repeat(numLineBreaks) +
|
|
// numLineBreaks < 2 will not properly delimit individual section
|
|
// but it does not hurt to test that anyway
|
|
(individualSectionName != null ?
|
|
"Name: " + individualSectionName + lineBreak +
|
|
"Ignore: nothing here" + lineBreak +
|
|
lineBreak
|
|
: "")
|
|
).getBytes(UTF_8);
|
|
}
|
|
|
|
@BeforeMethod
|
|
public void verbose() {
|
|
System.out.println("lineBreak = " + stringToIntList(lineBreak));
|
|
System.out.println("numLineBreaks = " + numLineBreaks);
|
|
System.out.println("individualSectionName = " + individualSectionName);
|
|
}
|
|
|
|
@FunctionalInterface
|
|
interface Callable {
|
|
void call() throws Exception;
|
|
}
|
|
|
|
void catchNoLineBreakAfterMainAttributes(Callable test) throws Exception {
|
|
// manifests cannot be parsed and digested if the main attributes do
|
|
// not end in a blank line (double line break) or one line break
|
|
// immediately before eof.
|
|
boolean failureExpected = numLineBreaks == 0
|
|
&& individualSectionName == null;
|
|
try {
|
|
test.call();
|
|
if (failureExpected) fail("expected an exception");
|
|
} catch (NullPointerException | IllegalStateException e) {
|
|
if (!failureExpected) fail("unexpected " + e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that the beginning of the manifest until position<ol>
|
|
* <li>{@code Jarsigner.findHeaderEnd} in the previous version
|
|
* and</li>
|
|
* <li>{@code ManifestDigester.getMainAttsEntry().sections[0].
|
|
* lengthWithBlankLine} in the new version</li>
|
|
* </ol>produce the same offset (TODO: or the same error).
|
|
* The beginning of the manifest until that offset (range
|
|
* <pre>0 .. (offset - 1)</pre>) will be reproduced if the manifest has
|
|
* not changed.
|
|
* <p>
|
|
* Getting {@code startOfNext} of {@link ManifestDigester#findSection}'s
|
|
* first invokation returned {@link ManifestDigester.Position} which
|
|
* identifies the end offset of the main attributes is difficulted by
|
|
* {@link ManifestDigester#findSection} being private and therefore not
|
|
* directly accessible.
|
|
*/
|
|
@Test
|
|
public void startOfNextLengthWithBlankLine() throws Exception {
|
|
catchNoLineBreakAfterMainAttributes(() ->
|
|
assertEquals(lengthWithBlankLine(), findHeaderEnd(rawBytes))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Due to its private visibility,
|
|
* {@link ManifestDigester.Section#lengthWithBlankLine} is not directly
|
|
* accessible. However, calling {@link ManifestDigester.Entry#digest}
|
|
* reveals {@code lengthWithBlankLine} as third parameter in
|
|
* <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>
|
|
* on line ManifestDigester.java:212.
|
|
* <p>
|
|
* This value is the same as {@code startOfNext} of
|
|
* {@link ManifestDigester#findSection}'s first invocation returned
|
|
* {@link ManifestDigester.Position} identifying the end offset of the
|
|
* main attributes because<ol>
|
|
* <li>the end offset of the main attributes is assigned to
|
|
* {@code startOfNext} in
|
|
* <pre>pos.startOfNext = i+1;</pre> in ManifestDigester.java:98</li>
|
|
* <li>which is then passed on as the third parameter to the constructor
|
|
* of a new {@link ManifestDigester.Section#Section} by
|
|
* <pre>new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)));</pre>
|
|
* in ManifestDigester.java:128</li>
|
|
* <li>where it is assigned to
|
|
* {@link ManifestDigester.Section#lengthWithBlankLine} by
|
|
* <pre>this.lengthWithBlankLine = lengthWithBlankLine;</pre>
|
|
* in ManifestDigester.java:241</li>
|
|
* <li>from where it is picked up by {@link ManifestDigester.Entry#digest}
|
|
* in
|
|
* <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>
|
|
* in ManifestDigester.java:212</li>
|
|
* </ol>
|
|
* all of which without any modification.
|
|
*/
|
|
int lengthWithBlankLine() {
|
|
int[] lengthWithBlankLine = new int[] { 0 };
|
|
new ManifestDigester(rawBytes).get(ManifestDigester.MF_MAIN_ATTRS,
|
|
false).digest(new MessageDigest("lengthWithBlankLine") {
|
|
@Override protected void engineReset() {
|
|
lengthWithBlankLine[0] = 0;
|
|
}
|
|
@Override protected void engineUpdate(byte b) {
|
|
lengthWithBlankLine[0]++;
|
|
}
|
|
@Override protected void engineUpdate(byte[] b, int o, int l) {
|
|
lengthWithBlankLine[0] += l;
|
|
}
|
|
@Override protected byte[] engineDigest() {
|
|
return null;
|
|
}
|
|
});
|
|
return lengthWithBlankLine[0];
|
|
}
|
|
|
|
/**
|
|
* Checks that the replacement of {@link JarSigner#findHeaderEnd} is
|
|
* actually used to reproduce manifest main attributes.
|
|
* <p>
|
|
* {@link #startOfNextLengthWithBlankLine} demonstrates that
|
|
* {@link JarSigner#findHeaderEnd} has been replaced successfully with
|
|
* {@link ManifestDigester#findSection} but does not also show that the
|
|
* main attributes are reproduced with the same offset as before.
|
|
* {@link #startOfNextLengthWithBlankLine} uses
|
|
* {@link ManifestDigester.Entry#digest} to demonstrate an equal offset
|
|
* calculated but {@link ManifestDigester.Entry#digest} is not necessarily
|
|
* the same as reproducing, especially when considering
|
|
* {@link ManifestDigester.Entry#oldStyle}.
|
|
*/
|
|
@Test(enabled = FIXED_8217375)
|
|
public void reproduceMainAttributes() throws Exception {
|
|
catchNoLineBreakAfterMainAttributes(() -> {
|
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
|
ManifestDigester md = new ManifestDigester(rawBytes);
|
|
// without 8217375 fixed the following line will not even compile
|
|
// so just remove it and skip the test for regression
|
|
md.getMainAttsEntry().reproduceRaw(buf); // FIXME
|
|
|
|
assertEquals(buf.size(), findHeaderEnd(rawBytes));
|
|
});
|
|
}
|
|
|
|
static List<Integer> stringToIntList(String string) {
|
|
byte[] bytes = string.getBytes(UTF_8);
|
|
List<Integer> list = new ArrayList<>();
|
|
for (int i = 0; i < bytes.length; i++) {
|
|
list.add((int) bytes[i]);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
}
|