test/jdk/sun/security/util/ManifestDigester/DigestInput.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.io.IOException;
       
    26 import java.security.MessageDigest;
       
    27 import java.util.ArrayList;
       
    28 import java.util.Arrays;
       
    29 import java.util.List;
       
    30 import java.util.jar.Attributes.Name;
       
    31 import java.util.stream.Collectors;
       
    32 
       
    33 import sun.security.util.ManifestDigester;
       
    34 
       
    35 import org.testng.annotations.DataProvider;
       
    36 import org.testng.annotations.Factory;
       
    37 import org.testng.annotations.BeforeMethod;
       
    38 import org.testng.annotations.AfterTest;
       
    39 import org.testng.annotations.Test;
       
    40 
       
    41 import static java.nio.charset.StandardCharsets.UTF_8;
       
    42 import static org.testng.Assert.*;
       
    43 
       
    44 /**
       
    45  * @test
       
    46  * @bug 8217375
       
    47  * @modules java.base/sun.security.util
       
    48  * @compile ../../tools/jarsigner/Utils.java
       
    49  * @run testng DigestInput
       
    50  * @summary Checks that the manifest main attributes and entry digests are the
       
    51  * same as before resolution of bug 8217375 which means they treat some white
       
    52  * space different for oldStyle or digestWorkaround except for the blank line
       
    53  * at the end of the manifest file for digestWorkaround.
       
    54  */
       
    55 public class DigestInput {
       
    56 
       
    57     /**
       
    58      * Filters some test cases for calibrating expected digests with previous
       
    59      * implementation. TODO: Delete this after calibrating with old sources.
       
    60      */
       
    61     static final boolean FIXED_8217375 = true; // FIXME
       
    62 
       
    63     /**
       
    64      * {@link ManifestDigester.Entry#digestWorkaround} should not feed the
       
    65      * trailing blank line into the digester. Before resolution of 8217375 it
       
    66      * fed the trailing blank line into the digest if the second line break
       
    67      * was at the end of the file due to <pre>
       
    68      * if (allBlank || (i == len-1)) {
       
    69      *     if (i == len-1)
       
    70      *         pos.endOfSection = i;
       
    71      *     else
       
    72      *         pos.endOfSection = last;
       
    73      * </pre> in {@link ManifestDigester#findSection}. In that case at the end
       
    74      * of the manifest file, {@link ManifestDigester.Entry#digestWorkaround}
       
    75      * would have produced the same digest as
       
    76      * {@link ManifestDigester.Entry#digest} which was wrong and without effect
       
    77      * at best.
       
    78      * <p>
       
    79      * Once this fix is accepted, this flag can be removed along with
       
    80      * {@link #assertDigestEqualsCatchWorkaroundBroken}.
       
    81      */
       
    82     static final boolean FIXED_8217375_EOF_ENDOFSECTION = FIXED_8217375;
       
    83 
       
    84     static final String SECTION_NAME = "some individual section name";
       
    85 
       
    86     @DataProvider(name = "parameters")
       
    87     public static Object[][] parameters() {
       
    88         List<Object[]> tests = new ArrayList<>();
       
    89         for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {
       
    90             if ("\r".equals(lineBreak) && !FIXED_8217375) continue;
       
    91             for (int addLB = 0; addLB <= 4; addLB++) {
       
    92                 for (int numSecs = 0; numSecs <= 4; numSecs++) {
       
    93                     for (boolean otherSec : new Boolean[] { false, true }) {
       
    94                         for (boolean oldStyle : new Boolean[] { false, true }) {
       
    95                             for (boolean workaround :
       
    96                                     new Boolean[] { false, true }) {
       
    97                                 tests.add(new Object[] {
       
    98                                     lineBreak, addLB, numSecs, otherSec,
       
    99                                             oldStyle, workaround
       
   100                                 });
       
   101                             }
       
   102                         }
       
   103                     }
       
   104                 }
       
   105             }
       
   106         }
       
   107         return tests.toArray(new Object[tests.size()][]);
       
   108     }
       
   109 
       
   110     @Factory(dataProvider = "parameters")
       
   111     public static Object[] createTests(
       
   112             String lineBreak, int additionalLineBreaks,
       
   113             int numberOfSections, boolean hasOtherSection,
       
   114             boolean oldStyle, boolean digestWorkaround) {
       
   115         return new Object[] { new DigestInput(lineBreak,
       
   116                 additionalLineBreaks, numberOfSections, hasOtherSection,
       
   117                 oldStyle, digestWorkaround)
       
   118         };
       
   119     }
       
   120 
       
   121     final String lineBreak;
       
   122     final int additionalLineBreaks; // number of blank lines delimiting section
       
   123     final int numberOfSections;
       
   124     final boolean hasOtherSection;
       
   125     final boolean oldStyle;
       
   126     final boolean digestWorkaround;
       
   127 
       
   128     public DigestInput(
       
   129             String lineBreak, int additionalLineBreaks,
       
   130             int numberOfSections, boolean hasOtherSection,
       
   131             boolean oldStyle, boolean digestWorkaround) {
       
   132         this.lineBreak = lineBreak;
       
   133         this.additionalLineBreaks = additionalLineBreaks;
       
   134         this.numberOfSections = numberOfSections;
       
   135         this.hasOtherSection = hasOtherSection;
       
   136         this.oldStyle = oldStyle;
       
   137         this.digestWorkaround = digestWorkaround;
       
   138     }
       
   139 
       
   140     @BeforeMethod
       
   141     public void verbose() {
       
   142         System.out.println("-".repeat(72));
       
   143         System.out.println("lineBreak = " +
       
   144                 Utils.escapeStringWithNumbers(lineBreak));
       
   145         System.out.println("additionalLineBreaks = " + additionalLineBreaks);
       
   146         System.out.println("numberOfSections = " + numberOfSections);
       
   147         System.out.println("hasOtherSection = " + hasOtherSection);
       
   148         System.out.println("oldStyle = " + oldStyle);
       
   149         System.out.println("digestWorkaround = " + digestWorkaround);
       
   150         System.out.println("-".repeat(72));
       
   151     }
       
   152 
       
   153     byte[] rawManifestBytes() {
       
   154         return (
       
   155             Name.MANIFEST_VERSION + ": 1.0" + lineBreak +
       
   156             "OldStyle0: no trailing space" + lineBreak +
       
   157             "OldStyle1: trailing space " + lineBreak +
       
   158             "OldStyle2: two trailing spaces  " + lineBreak +
       
   159                     lineBreak.repeat(additionalLineBreaks) +
       
   160             (
       
   161                 "Name: " + SECTION_NAME + lineBreak +
       
   162                 "OldStyle0: no trailing space" + lineBreak +
       
   163                 "OldStyle1: trailing space " + lineBreak +
       
   164                 "OldStyle2: two trailing spaces  " + lineBreak +
       
   165                 lineBreak.repeat(additionalLineBreaks)
       
   166             ).repeat(numberOfSections) +
       
   167             (hasOtherSection ?
       
   168                 "Name: unrelated trailing section" + lineBreak +
       
   169                 "OldStyle0: no trailing space" + lineBreak +
       
   170                 "OldStyle1: trailing space " + lineBreak +
       
   171                 "OldStyle2: two trailing spaces  " + lineBreak +
       
   172                 lineBreak.repeat(additionalLineBreaks)
       
   173             : "")
       
   174         ).getBytes(UTF_8);
       
   175     }
       
   176 
       
   177     byte[] expectedMainAttrsDigest(boolean digestWorkaround) {
       
   178         return (
       
   179             Name.MANIFEST_VERSION + ": 1.0" + lineBreak +
       
   180             "OldStyle0: no trailing space" + lineBreak +
       
   181             "OldStyle1: trailing space" +
       
   182                 (!oldStyle || !lineBreak.startsWith("\r") || digestWorkaround ?
       
   183                     " " : "") + lineBreak +
       
   184             "OldStyle2: two trailing spaces " +
       
   185                 (!oldStyle || !lineBreak.startsWith("\r") || digestWorkaround ?
       
   186                         " " : "") + lineBreak +
       
   187             (
       
   188                      (
       
   189                                  !digestWorkaround
       
   190                          || (
       
   191                                  additionalLineBreaks == 1
       
   192                               && numberOfSections == 0
       
   193                               && !hasOtherSection
       
   194                               && (
       
   195                                       digestWorkaround
       
   196                                    && !FIXED_8217375_EOF_ENDOFSECTION
       
   197                                  )
       
   198                          )
       
   199                 ) && (
       
   200                             additionalLineBreaks > 0
       
   201                          || numberOfSections > 0
       
   202                          || hasOtherSection
       
   203                 )
       
   204             ? lineBreak : "")
       
   205         ).getBytes(UTF_8);
       
   206     }
       
   207 
       
   208     byte[] expectedIndividualSectionDigest(boolean digestWorkaround) {
       
   209         if (numberOfSections == 0) return null;
       
   210         return (
       
   211             (
       
   212                 "Name: " + SECTION_NAME + lineBreak +
       
   213                 "OldStyle0: no trailing space" + lineBreak +
       
   214                 "OldStyle1: trailing space" +
       
   215                     (!oldStyle || !lineBreak.startsWith("\r")
       
   216                             || digestWorkaround ? " " : "") + lineBreak +
       
   217                 "OldStyle2: two trailing spaces " +
       
   218                     (!oldStyle || !lineBreak.startsWith("\r")
       
   219                             || digestWorkaround ? " " : "") + lineBreak +
       
   220                 (
       
   221                     (
       
   222                            !digestWorkaround
       
   223                     ) && (
       
   224                            additionalLineBreaks > 0
       
   225                     )
       
   226                 ? lineBreak : "")
       
   227             ).repeat(numberOfSections) +
       
   228             (
       
   229                    additionalLineBreaks == 1
       
   230                 && !hasOtherSection
       
   231                 && digestWorkaround
       
   232                 && !FIXED_8217375_EOF_ENDOFSECTION
       
   233             ? lineBreak : "")
       
   234         ).getBytes(UTF_8);
       
   235     }
       
   236 
       
   237     class EchoMessageDigest extends MessageDigest {
       
   238 
       
   239         ByteArrayOutputStream buf;
       
   240 
       
   241         EchoMessageDigest() {
       
   242             super("echo");
       
   243         }
       
   244 
       
   245         @Override
       
   246         protected void engineReset() {
       
   247             buf = new ByteArrayOutputStream();
       
   248         }
       
   249 
       
   250         @Override
       
   251         protected void engineUpdate(byte input) {
       
   252             buf.write(input);
       
   253         }
       
   254 
       
   255         @Override
       
   256         protected void engineUpdate(byte[] i, int o, int l) {
       
   257             buf.write(i, o, l);
       
   258         }
       
   259 
       
   260         @Override protected byte[] engineDigest() {
       
   261             return buf.toByteArray();
       
   262         }
       
   263 
       
   264     }
       
   265 
       
   266     byte[] digestMainAttributes(byte[] mfBytes) throws Exception {
       
   267         Utils.echoManifest(mfBytes, "going to digest main attributes of");
       
   268 
       
   269         ManifestDigester md = new ManifestDigester(mfBytes);
       
   270         ManifestDigester.Entry entry =
       
   271                 md.get(ManifestDigester.MF_MAIN_ATTRS, oldStyle);
       
   272         MessageDigest digester = new EchoMessageDigest();
       
   273         return digestWorkaround ?
       
   274                 entry.digestWorkaround(digester) : entry.digest(digester);
       
   275     }
       
   276 
       
   277     byte[] digestIndividualSection(byte[] mfBytes) throws Exception {
       
   278         Utils.echoManifest(mfBytes,
       
   279                 "going to digest section " + SECTION_NAME + " of");
       
   280 
       
   281         ManifestDigester md = new ManifestDigester(mfBytes);
       
   282         ManifestDigester.Entry entry = md.get(SECTION_NAME, oldStyle);
       
   283         if (entry == null) {
       
   284             return null;
       
   285         }
       
   286         MessageDigest digester = new EchoMessageDigest();
       
   287         return digestWorkaround ?
       
   288                 entry.digestWorkaround(digester) : entry.digest(digester);
       
   289     }
       
   290 
       
   291 
       
   292     /**
       
   293      * Checks that the manifest main attributes digest is the same as before.
       
   294      */
       
   295     @Test
       
   296     public void testMainAttributesDigest() throws Exception {
       
   297         byte[] mfRaw = rawManifestBytes();
       
   298         byte[] digest = digestMainAttributes(mfRaw);
       
   299         byte[] expectedDigest = expectedMainAttrsDigest(digestWorkaround);
       
   300 
       
   301         // the individual section will be digested along with the main
       
   302         // attributes if not properly delimited with a blank line
       
   303         if (additionalLineBreaks == 0
       
   304                 && (numberOfSections > 0 || hasOtherSection)) {
       
   305             assertNotEquals(digest, expectedDigest);
       
   306             return;
       
   307         }
       
   308 
       
   309         byte[] expectedDigestNoWorkaround = expectedMainAttrsDigest(false);
       
   310 
       
   311 //        assertDigestEquals(digest, expectedDigest); // FIXME
       
   312         assertDigestEqualsCatchWorkaroundBroken(
       
   313                 digest, expectedDigest, expectedDigestNoWorkaround);
       
   314     }
       
   315 
       
   316     /**
       
   317      * Checks that an individual section digest is the same as before.
       
   318      */
       
   319     @Test
       
   320     public void testIndividualSectionDigest() throws Exception {
       
   321         byte[] mfRaw = rawManifestBytes();
       
   322         byte[] digest = digestIndividualSection(mfRaw);
       
   323 
       
   324         // no digest will be produced for an individual section that is not
       
   325         // properly section delimited with a blank line.
       
   326         byte[] expectedDigest =
       
   327                 additionalLineBreaks == 0 ? null :
       
   328                     expectedIndividualSectionDigest(digestWorkaround);
       
   329 
       
   330         byte[] expectedDigestNoWorkaround =
       
   331                 additionalLineBreaks == 0 ? null :
       
   332                     expectedIndividualSectionDigest(false);
       
   333 
       
   334 //      assertDigestEquals(digest, expectedDigest); // FIXME
       
   335         assertDigestEqualsCatchWorkaroundBroken(
       
   336                 digest, expectedDigest, expectedDigestNoWorkaround);
       
   337     }
       
   338 
       
   339     static int firstDiffPos = Integer.MAX_VALUE;
       
   340 
       
   341     /**
       
   342      * @see FIXED_8217375_EOF_ENDOFSECTION
       
   343      */
       
   344     void assertDigestEqualsCatchWorkaroundBroken(
       
   345             byte[] actual, byte[] expected, byte[] expectedNoWorkaround)
       
   346                     throws IOException {
       
   347         try {
       
   348             assertDigestEquals(actual, expected);
       
   349         } catch (AssertionError e) {
       
   350             if (digestWorkaround && FIXED_8217375_EOF_ENDOFSECTION &&
       
   351                     Arrays.equals(expected, expectedNoWorkaround)) {
       
   352                 // if digests with and without workaround are the same anyway
       
   353                 // the workaround has failed and could not have worked with
       
   354                 // the same digest as produced without workaround before
       
   355                 // which would not match either because equal.
       
   356                 return;
       
   357             }
       
   358             fail("failed also without digestWorkaound", e);
       
   359         }
       
   360     }
       
   361 
       
   362     void assertDigestEquals(byte[] actual, byte[] expected) throws IOException {
       
   363         if (actual == null && expected == null) return;
       
   364         Utils.echoManifest(actual, "actual digest");
       
   365         Utils.echoManifest(expected, "expected digest");
       
   366         for (int i = 0; i < actual.length && i < expected.length; i++) {
       
   367             if (actual[i] != expected[i]) {
       
   368                 firstDiffPos = Math.min(firstDiffPos, i);
       
   369                 verbose();
       
   370                 fail("found first difference in current test"
       
   371                         + " at position " + i);
       
   372             }
       
   373         }
       
   374         if (actual.length != expected.length) {
       
   375             int diffPos = Math.min(actual.length, expected.length);
       
   376             firstDiffPos = Math.min(firstDiffPos, diffPos);
       
   377             verbose();
       
   378             fail("found first difference in current test"
       
   379                     + " at position " + diffPos + " after one digest end");
       
   380         }
       
   381         assertEquals(actual, expected);
       
   382     }
       
   383 
       
   384     @AfterTest
       
   385     public void reportFirstDiffPos() {
       
   386         System.err.println("found first difference in all tests"
       
   387                 + " at position " + firstDiffPos);
       
   388     }
       
   389 
       
   390 }