jdk-24/test/jdk/sun/security/util/ManifestDigester/DigestInput.java
2019-07-18 08:53:06 +08:00

391 lines
15 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.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);
}
}