test/jdk/sun/security/tools/jarsigner/FindHeaderEndVsManifestDigesterFindFirstSection.java
changeset 57488 94691d8e746f
equal deleted inserted replaced
57487:643978a35f6e 57488:94691d8e746f
       
     1 /*
       
     2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 import java.io.ByteArrayOutputStream;
       
    25 import java.security.MessageDigest;
       
    26 import java.util.ArrayList;
       
    27 import java.util.List;
       
    28 import sun.security.util.ManifestDigester;
       
    29 
       
    30 import org.testng.annotations.Test;
       
    31 import org.testng.annotations.DataProvider;
       
    32 import org.testng.annotations.Factory;
       
    33 import org.testng.annotations.BeforeClass;
       
    34 import org.testng.annotations.BeforeMethod;
       
    35 
       
    36 import static java.nio.charset.StandardCharsets.UTF_8;
       
    37 import static org.testng.Assert.*;
       
    38 
       
    39 /**
       
    40  * @test
       
    41  * @bug 8217375
       
    42  * @modules java.base/sun.security.util
       
    43  * @run testng FindHeaderEndVsManifestDigesterFindFirstSection
       
    44  * @summary Checks that {@link JarSigner#findHeaderEnd} (moved to now
       
    45  * {@link #findHeaderEnd} in this test) can be replaced with
       
    46  * {@link ManifestDigester#findSection}
       
    47  * (first invocation will identify main attributes)
       
    48  * without making a difference.
       
    49  */
       
    50 /*
       
    51  * Note to future maintainer:
       
    52  * While it might look at first glance like this test ensures backwards-
       
    53  * compatibility between JarSigner.findHeaderEnd and
       
    54  * ManifestDigester.findSection's first invocation that find the main
       
    55  * attributes section, at the time of that change, this test continues to
       
    56  * verify main attributes digestion now with ManifestDigester.findSection as
       
    57  * opposed to previous implementation in JarSigner.findHeaderEnd.
       
    58  * Before completely removing this test, make sure that main attributes
       
    59  * digestion is covered appropriately with tests. After JarSigner.findHeaderEnd
       
    60  * has been removed digests should still continue to match.
       
    61  *
       
    62  * See also
       
    63  * - jdk/test/jdk/sun/security/tools/jarsigner/PreserveRawManifestEntryAndDigest.java
       
    64  * for some end-to-end tests utilizing the jarsigner tool,
       
    65  * - jdk/test/jdk/sun/security/util/ManifestDigester/FindSection.java and
       
    66  * - jdk/test/jdk/sun/security/util/ManifestDigester/DigestInput.java
       
    67  * for much more detailed tests at api level
       
    68  *
       
    69  * Both test mentioned above, however, originally were created when removing
       
    70  * confusion of "Manifest-Main-Attributes" individual section with actual main
       
    71  * attributes whereas the test here is about changes related to raw manifest
       
    72  * reproduction and in the end test pretty much the same behavior.
       
    73  */
       
    74 public class FindHeaderEndVsManifestDigesterFindFirstSection {
       
    75 
       
    76     static final boolean FIXED_8217375 = true; // FIXME
       
    77 
       
    78     /**
       
    79      * from former {@link JarSigner#findHeaderEnd}, subject to verification if
       
    80      * it can be replaced with {@link ManifestDigester#findSection}
       
    81      */
       
    82     @SuppressWarnings("fallthrough")
       
    83     private int findHeaderEnd(byte[] bs) {
       
    84         // Initial state true to deal with empty header
       
    85         boolean newline = true;     // just met a newline
       
    86         int len = bs.length;
       
    87         for (int i = 0; i < len; i++) {
       
    88             switch (bs[i]) {
       
    89                 case '\r':
       
    90                     if (i < len - 1 && bs[i + 1] == '\n') i++;
       
    91                     // fallthrough
       
    92                 case '\n':
       
    93                     if (newline) return i + 1;    //+1 to get length
       
    94                     newline = true;
       
    95                     break;
       
    96                 default:
       
    97                     newline = false;
       
    98             }
       
    99         }
       
   100         // If header end is not found, it means the MANIFEST.MF has only
       
   101         // the main attributes section and it does not end with 2 newlines.
       
   102         // Returns the whole length so that it can be completely replaced.
       
   103         return len;
       
   104     }
       
   105 
       
   106     @DataProvider(name = "parameters")
       
   107     public static Object[][] parameters() {
       
   108         List<Object[]> tests = new ArrayList<>();
       
   109         for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {
       
   110             if ("\r".equals(lineBreak) && !FIXED_8217375) continue;
       
   111             for (int numLBs = 0; numLBs <= 3; numLBs++) {
       
   112                 for (String addSection : new String[] { null, "Ignore" }) {
       
   113                     tests.add(new Object[] { lineBreak, numLBs, addSection });
       
   114                 }
       
   115             }
       
   116         }
       
   117         return tests.toArray(new Object[tests.size()][]);
       
   118     }
       
   119 
       
   120     @Factory(dataProvider = "parameters")
       
   121     public static Object[] createTests(String lineBreak, int numLineBreaks,
       
   122             String individualSectionName) {
       
   123         return new Object[]{new FindHeaderEndVsManifestDigesterFindFirstSection(
       
   124                 lineBreak, numLineBreaks, individualSectionName
       
   125         )};
       
   126     }
       
   127 
       
   128     final String lineBreak;
       
   129     final int numLineBreaks; // number of line breaks after main attributes
       
   130     final String individualSectionName; // null means only main attributes
       
   131     final byte[] rawBytes;
       
   132 
       
   133     FindHeaderEndVsManifestDigesterFindFirstSection(String lineBreak,
       
   134             int numLineBreaks, String individualSectionName) {
       
   135         this.lineBreak = lineBreak;
       
   136         this.numLineBreaks = numLineBreaks;
       
   137         this.individualSectionName = individualSectionName;
       
   138 
       
   139         rawBytes = (
       
   140             "oldStyle: trailing space " + lineBreak +
       
   141             "newStyle: no trailing space" + lineBreak.repeat(numLineBreaks) +
       
   142             // numLineBreaks < 2 will not properly delimit individual section
       
   143             // but it does not hurt to test that anyway
       
   144             (individualSectionName != null ?
       
   145                     "Name: " + individualSectionName + lineBreak +
       
   146                     "Ignore: nothing here" + lineBreak +
       
   147                     lineBreak
       
   148                 : "")
       
   149         ).getBytes(UTF_8);
       
   150     }
       
   151 
       
   152     @BeforeMethod
       
   153     public void verbose() {
       
   154         System.out.println("lineBreak = " + stringToIntList(lineBreak));
       
   155         System.out.println("numLineBreaks = " + numLineBreaks);
       
   156         System.out.println("individualSectionName = " + individualSectionName);
       
   157     }
       
   158 
       
   159     @FunctionalInterface
       
   160     interface Callable {
       
   161         void call() throws Exception;
       
   162     }
       
   163 
       
   164     void catchNoLineBreakAfterMainAttributes(Callable test) throws Exception {
       
   165         // manifests cannot be parsed and digested if the main attributes do
       
   166         // not end in a blank line (double line break) or one line break
       
   167         // immediately before eof.
       
   168         boolean failureExpected = numLineBreaks == 0
       
   169                 && individualSectionName == null;
       
   170         try {
       
   171             test.call();
       
   172             if (failureExpected) fail("expected an exception");
       
   173         } catch (NullPointerException | IllegalStateException e) {
       
   174             if (!failureExpected) fail("unexpected " + e.getMessage(), e);
       
   175         }
       
   176     }
       
   177 
       
   178     /**
       
   179      * Checks that the beginning of the manifest until position<ol>
       
   180      * <li>{@code Jarsigner.findHeaderEnd} in the previous version
       
   181      * and</li>
       
   182      * <li>{@code ManifestDigester.getMainAttsEntry().sections[0].
       
   183      * lengthWithBlankLine} in the new version</li>
       
   184      * </ol>produce the same offset (TODO: or the same error).
       
   185      * The beginning of the manifest until that offset (range
       
   186      * <pre>0 .. (offset - 1)</pre>) will be reproduced if the manifest has
       
   187      * not changed.
       
   188      * <p>
       
   189      * Getting {@code startOfNext} of {@link ManifestDigester#findSection}'s
       
   190      * first invokation returned {@link ManifestDigester.Position} which
       
   191      * identifies the end offset of the main attributes is difficulted by
       
   192      * {@link ManifestDigester#findSection} being private and therefore not
       
   193      * directly accessible.
       
   194      */
       
   195     @Test
       
   196     public void startOfNextLengthWithBlankLine() throws Exception {
       
   197         catchNoLineBreakAfterMainAttributes(() ->
       
   198             assertEquals(lengthWithBlankLine(), findHeaderEnd(rawBytes))
       
   199         );
       
   200     }
       
   201 
       
   202     /**
       
   203      * Due to its private visibility,
       
   204      * {@link ManifestDigester.Section#lengthWithBlankLine} is not directly
       
   205      * accessible. However, calling {@link ManifestDigester.Entry#digest}
       
   206      * reveals {@code lengthWithBlankLine} as third parameter in
       
   207      * <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>
       
   208      * on line ManifestDigester.java:212.
       
   209      * <p>
       
   210      * This value is the same as {@code startOfNext} of
       
   211      * {@link ManifestDigester#findSection}'s first invocation returned
       
   212      * {@link ManifestDigester.Position} identifying the end offset of the
       
   213      * main attributes because<ol>
       
   214      * <li>the end offset of the main attributes is assigned to
       
   215      * {@code startOfNext} in
       
   216      * <pre>pos.startOfNext = i+1;</pre> in ManifestDigester.java:98</li>
       
   217      * <li>which is then passed on as the third parameter to the constructor
       
   218      * of a new {@link ManifestDigester.Section#Section} by
       
   219      * <pre>new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)));</pre>
       
   220      * in in ManifestDigester.java:128</li>
       
   221      * <li>where it is assigned to
       
   222      * {@link ManifestDigester.Section#lengthWithBlankLine} by
       
   223      * <pre>this.lengthWithBlankLine = lengthWithBlankLine;</pre>
       
   224      * in ManifestDigester.java:241</li>
       
   225      * <li>from where it is picked up by {@link ManifestDigester.Entry#digest}
       
   226      * in
       
   227      * <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>
       
   228      * in ManifestDigester.java:212</li>
       
   229      * </ol>
       
   230      * all of which without any modification.
       
   231      */
       
   232     int lengthWithBlankLine() {
       
   233         int[] lengthWithBlankLine = new int[] { 0 };
       
   234         new ManifestDigester(rawBytes).get(ManifestDigester.MF_MAIN_ATTRS,
       
   235                 false).digest(new MessageDigest("lengthWithBlankLine") {
       
   236             @Override protected void engineReset() {
       
   237                 lengthWithBlankLine[0] = 0;
       
   238             }
       
   239             @Override protected void engineUpdate(byte b) {
       
   240                 lengthWithBlankLine[0]++;
       
   241             }
       
   242             @Override protected void engineUpdate(byte[] b, int o, int l) {
       
   243                 lengthWithBlankLine[0] += l;
       
   244             }
       
   245             @Override protected byte[] engineDigest() {
       
   246                 return null;
       
   247             }
       
   248         });
       
   249         return lengthWithBlankLine[0];
       
   250     }
       
   251 
       
   252     /**
       
   253      * Checks that the replacement of {@link JarSigner#findHeaderEnd} is
       
   254      * actually used to reproduce manifest main attributes.
       
   255      * <p>
       
   256      * {@link #startOfNextLengthWithBlankLine} demonstrates that
       
   257      * {@link JarSigner#findHeaderEnd} has been replaced successfully with
       
   258      * {@link ManifestDigester#findSection} but does not also show that the
       
   259      * main attributes are reproduced with the same offset as before.
       
   260      * {@link #startOfNextLengthWithBlankLine} uses
       
   261      * {@link ManifestDigester.Entry#digest} to demonstrate an equal offset
       
   262      * calculated but {@link ManifestDigester.Entry#digest} is not necessarily
       
   263      * the same as reproducing, especially when considering
       
   264      * {@link ManifestDigester.Entry#oldStyle}.
       
   265      */
       
   266     @Test(enabled = FIXED_8217375)
       
   267     public void reproduceMainAttributes() throws Exception {
       
   268         catchNoLineBreakAfterMainAttributes(() -> {
       
   269             ByteArrayOutputStream buf = new ByteArrayOutputStream();
       
   270             ManifestDigester md = new ManifestDigester(rawBytes);
       
   271             // without 8217375 fixed the following line will not even compile
       
   272             // so just remove it and skip the test for regression
       
   273             md.getMainAttsEntry().reproduceRaw(buf); // FIXME
       
   274 
       
   275             assertEquals(buf.size(), findHeaderEnd(rawBytes));
       
   276         });
       
   277     }
       
   278 
       
   279     static List<Integer> stringToIntList(String string) {
       
   280         byte[] bytes = string.getBytes(UTF_8);
       
   281         List<Integer> list = new ArrayList<>();
       
   282         for (int i = 0; i < bytes.length; i++) {
       
   283             list.add((int) bytes[i]);
       
   284         }
       
   285         return list;
       
   286     }
       
   287 
       
   288 }