test/jdk/sun/security/util/ManifestDigester/ReproduceRaw.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.ByteArrayInputStream;
       
    25 import java.io.ByteArrayOutputStream;
       
    26 import java.io.IOException;
       
    27 import java.security.MessageDigest;
       
    28 import java.util.ArrayList;
       
    29 import java.util.List;
       
    30 import java.util.stream.Collectors;
       
    31 import java.util.jar.Manifest;
       
    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.Test;
       
    39 
       
    40 import static java.nio.charset.StandardCharsets.UTF_8;
       
    41 import static org.testng.Assert.*;
       
    42 
       
    43 /**
       
    44  * @test
       
    45  * @bug 8217375
       
    46  * @modules java.base/sun.security.util
       
    47  * @compile ../../tools/jarsigner/Utils.java
       
    48  * @run testng ReproduceRaw
       
    49  * @summary Verifies that {@link ManifestDigester} can reproduce parts of
       
    50  * manifests in their binary form so that {@link JarSigner} can rely on
       
    51  * {@link ManifestDigester.Entry#reproduceRaw} to write in a map view
       
    52  * unmodified entries back also unmodified in their binary form.
       
    53  * <p>
       
    54  * See also<ul>
       
    55  * <li>{@link PreserveRawManifestEntryAndDigest} with end to end tests
       
    56  * with {@code jarsigner} tool and</li>
       
    57  * <li>{@link FindHeaderEndVsManifestDigesterFindFirstSection} about
       
    58  * identifying the binary portion of only main attributes and more extensive
       
    59  * main attributes digesting tests while this one test here is more about
       
    60  * reproducing individual sections and that they result in the same
       
    61  * digests.</li>
       
    62  * </ul>
       
    63  */
       
    64 public class ReproduceRaw {
       
    65 
       
    66     static final boolean VERBOSE = false;
       
    67 
       
    68     @DataProvider(name = "parameters")
       
    69     public static Object[][] parameters() {
       
    70         List<Object[]> tests = new ArrayList<>();
       
    71         for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {
       
    72             for (boolean oldStyle : new Boolean[] { false, true }) {
       
    73                 for (boolean workaround : new Boolean[] { false, true }) {
       
    74                     tests.add(new Object[] { lineBreak, oldStyle, workaround });
       
    75                 }
       
    76             }
       
    77         }
       
    78         return tests.toArray(new Object[tests.size()][]);
       
    79     }
       
    80 
       
    81     @Factory(dataProvider = "parameters")
       
    82     public static Object[] createTests(String lineBreak,
       
    83             boolean oldStyle, boolean digestWorkaround) {
       
    84         return new Object[]{
       
    85                 new ReproduceRaw(lineBreak, oldStyle, digestWorkaround)
       
    86         };
       
    87     }
       
    88 
       
    89     final String lineBreak;
       
    90     final boolean oldStyle;
       
    91     final boolean digestWorkaround;
       
    92 
       
    93     public ReproduceRaw(String lineBreak,
       
    94             boolean oldStyle, boolean digestWorkaround) {
       
    95         this.lineBreak = lineBreak;
       
    96         this.oldStyle = oldStyle;
       
    97         this.digestWorkaround = digestWorkaround;
       
    98     }
       
    99 
       
   100     @BeforeMethod
       
   101     public void verbose() {
       
   102         System.out.println("lineBreak = " +
       
   103                 Utils.escapeStringWithNumbers(lineBreak));
       
   104         System.out.println("oldStyle = " + oldStyle);
       
   105         System.out.println("digestWorkaround = " + digestWorkaround);
       
   106     }
       
   107 
       
   108     class EchoMessageDigest extends MessageDigest {
       
   109 
       
   110         ByteArrayOutputStream buf;
       
   111 
       
   112         EchoMessageDigest() {
       
   113             super("echo");
       
   114         }
       
   115 
       
   116         @Override
       
   117         protected void engineReset() {
       
   118             buf = new ByteArrayOutputStream();
       
   119         }
       
   120 
       
   121         @Override
       
   122         protected void engineUpdate(byte input) {
       
   123             buf.write(input);
       
   124         }
       
   125 
       
   126         @Override
       
   127         protected void engineUpdate(byte[] i, int o, int l) {
       
   128             buf.write(i, o, l);
       
   129         }
       
   130 
       
   131         @Override protected byte[] engineDigest() {
       
   132             return buf.toByteArray();
       
   133         }
       
   134 
       
   135     }
       
   136 
       
   137     /**
       
   138      * similar to corresponding part of {@link JarSigner#sign0}
       
   139      * (stripped down to the code for reproducing the old manifest entry by
       
   140      * entry which was too difficult to achieve using the real JarSigner code
       
   141      * in the test here)
       
   142      */
       
   143     byte[] reproduceRawManifest(byte[] mfRawBytes,
       
   144             boolean mainAttsProperlyDelimited,
       
   145             boolean sectionProperlyDelimited) throws IOException {
       
   146         Manifest manifest = new Manifest(new ByteArrayInputStream(mfRawBytes));
       
   147         ManifestDigester oldMd = new ManifestDigester(mfRawBytes);
       
   148         ByteArrayOutputStream baos = new ByteArrayOutputStream();
       
   149 
       
   150         // main attributes
       
   151         assertEquals(oldMd.getMainAttsEntry().isProperlyDelimited(),
       
   152                 mainAttsProperlyDelimited);
       
   153         oldMd.getMainAttsEntry().reproduceRaw(baos);
       
   154 
       
   155         // individual sections
       
   156         for (String key : manifest.getEntries().keySet()) {
       
   157             assertEquals(oldMd.get(key).isProperlyDelimited(),
       
   158                     sectionProperlyDelimited);
       
   159             oldMd.get(key).reproduceRaw(baos);
       
   160         }
       
   161 
       
   162         return baos.toByteArray();
       
   163     }
       
   164 
       
   165     static String regExscape(String expr) {
       
   166         for (int i = 0; i < expr.length(); i++) {
       
   167             if (expr.charAt(i) == '\r' || expr.charAt(i) == '\n') {
       
   168                 expr = expr.substring(0, i) + "\\" + expr.substring(i++);
       
   169             }
       
   170         }
       
   171         return expr;
       
   172     }
       
   173 
       
   174     byte[] digest(byte[] manifest, String section) {
       
   175         MessageDigest digester = new EchoMessageDigest();
       
   176         ManifestDigester md = new ManifestDigester(manifest);
       
   177         ManifestDigester.Entry entry = section == null ?
       
   178                 md.getMainAttsEntry(oldStyle) : md.get(section, oldStyle);
       
   179         return digestWorkaround ?
       
   180                 entry.digestWorkaround(digester) :
       
   181                 entry.digest(digester);
       
   182     }
       
   183 
       
   184     void test(byte[] originalManifest, boolean mainAttsProperlyDelimited,
       
   185             boolean sectionProperlyDelimited) throws Exception {
       
   186         Utils.echoManifest(originalManifest, "original manifest");
       
   187         byte[] reproducedManifest = reproduceRawManifest(originalManifest,
       
   188                 mainAttsProperlyDelimited, sectionProperlyDelimited);
       
   189         Utils.echoManifest(reproducedManifest, "reproduced manifest");
       
   190 
       
   191         // The reproduced manifest is not necessarily completely identical to
       
   192         // the original if it contained superfluous blank lines.
       
   193         // It's sufficient that the digests are equal and as an additional
       
   194         // check, the reproduced manifest is here compared to the original
       
   195         // without more than double line breaks.
       
   196         if (!lineBreak.repeat(2).equals(new String(originalManifest, UTF_8))) {
       
   197             assertEquals(
       
   198                 new String(reproducedManifest, UTF_8),
       
   199                 new String(originalManifest, UTF_8).replaceAll(
       
   200                     regExscape(lineBreak) + "(" + regExscape(lineBreak) + ")+",
       
   201                     lineBreak.repeat(2)));
       
   202         }
       
   203 
       
   204         // compare digests of reproduced manifest entries with digests of
       
   205         // original manifest entries
       
   206         assertEquals(digest(originalManifest, null),
       
   207                 digest(reproducedManifest, null));
       
   208         for (String key : new Manifest(new ByteArrayInputStream(
       
   209                 originalManifest)).getEntries().keySet()) {
       
   210             assertEquals(digest(originalManifest, key),
       
   211                     digest(reproducedManifest, key));
       
   212         }
       
   213 
       
   214         // parse and compare original and reproduced manifests as manifests
       
   215         assertEquals(new Manifest(new ByteArrayInputStream(originalManifest)),
       
   216                 new Manifest(new ByteArrayInputStream(reproducedManifest)));
       
   217     }
       
   218 
       
   219     void test(byte[] originalManifest, boolean mainAttsProperlyDelimited)
       
   220             throws Exception {
       
   221         // assert all individual sections properly delimited particularly useful
       
   222         // when no individual sections present
       
   223         test(originalManifest, mainAttsProperlyDelimited, true);
       
   224     }
       
   225 
       
   226     @Test
       
   227     public void testManifestStartsWithBlankLine() throws Exception {
       
   228         test(lineBreak.getBytes(UTF_8), true);
       
   229         test(lineBreak.repeat(2).getBytes(UTF_8), true);
       
   230     }
       
   231 
       
   232     @Test
       
   233     public void testEOFAndNoLineBreakAfterMainAttributes() throws Exception {
       
   234         assertThrows(RuntimeException.class, () ->
       
   235             test("Manifest-Version: 1.0".getBytes(UTF_8), false)
       
   236         );
       
   237     }
       
   238 
       
   239     @Test
       
   240     public void testEOFAndNoBlankLineAfterMainAttributes() throws Exception {
       
   241         test(("Manifest-Version: 1.0" + lineBreak).getBytes(UTF_8), false);
       
   242     }
       
   243 
       
   244     @Test
       
   245     public void testNormalMainAttributes() throws Exception {
       
   246         test(("Manifest-Version: 1.0" +
       
   247                 lineBreak.repeat(2)).getBytes(UTF_8), true);
       
   248     }
       
   249 
       
   250     @Test
       
   251     public void testExtraLineBreakAfterMainAttributes() throws Exception {
       
   252         test(("Manifest-Version: 1.0" +
       
   253                 lineBreak.repeat(3)).getBytes(UTF_8), true);
       
   254     }
       
   255 
       
   256     @Test
       
   257     public void testIndividualSectionNoLineBreak() throws Exception {
       
   258         assertNull(new ManifestDigester((
       
   259                 "Manifest-Version: 1.0" + lineBreak +
       
   260                 lineBreak +
       
   261                 "Name: Section-Name" + lineBreak +
       
   262                 "Key: Value"
       
   263         ).getBytes(UTF_8)).get("Section-Name"));
       
   264     }
       
   265 
       
   266     @Test
       
   267     public void testIndividualSectionOneLineBreak() throws Exception {
       
   268         test((
       
   269                 "Manifest-Version: 1.0" + lineBreak +
       
   270                 lineBreak +
       
   271                 "Name: Section-Name" + lineBreak +
       
   272                 "Key: Value" + lineBreak
       
   273         ).getBytes(UTF_8), true, false);
       
   274     }
       
   275 
       
   276     @Test
       
   277     public void testNormalIndividualSectionTwoLineBreak() throws Exception {
       
   278         test((
       
   279                 "Manifest-Version: 1.0" + lineBreak +
       
   280                 lineBreak +
       
   281                 "Name: Section-Name" + lineBreak +
       
   282                 "Key: Value" + lineBreak.repeat(2)
       
   283         ).getBytes(UTF_8), true, true);
       
   284     }
       
   285 
       
   286     @Test
       
   287     public void testExtraLineBreakAfterIndividualSection() throws Exception {
       
   288         test((
       
   289                 "Manifest-Version: 1.0" + lineBreak +
       
   290                 lineBreak +
       
   291                 "Name: Section-Name" + lineBreak +
       
   292                 "Key: Value" + lineBreak.repeat(3)
       
   293                 ).getBytes(UTF_8), true, true);
       
   294     }
       
   295 
       
   296     @Test
       
   297     public void testIndividualSections() throws Exception {
       
   298         test((
       
   299                 "Manifest-Version: 1.0" + lineBreak +
       
   300                 lineBreak +
       
   301                 "Name: Section-Name" + lineBreak +
       
   302                 "Key: Value" + lineBreak +
       
   303                 lineBreak +
       
   304                 "Name: Section-Name" + lineBreak +
       
   305                 "Key: Value" + lineBreak +
       
   306                 lineBreak
       
   307                 ).getBytes(UTF_8), true, true);
       
   308     }
       
   309 
       
   310     @Test
       
   311     public void testExtraLineBreakBetweenIndividualSections() throws Exception {
       
   312         test((
       
   313                 "Manifest-Version: 1.0" + lineBreak +
       
   314                 lineBreak +
       
   315                 "Name: Section-Name" + lineBreak +
       
   316                 "Key: Value" + lineBreak +
       
   317                 lineBreak.repeat(2) +
       
   318                 "Name: Section-Name" + lineBreak +
       
   319                 "Key: Value" + lineBreak +
       
   320                 lineBreak
       
   321                 ).getBytes(UTF_8), true, true);
       
   322     }
       
   323 
       
   324 }