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")
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")
createTests(String lineBreak, boolean oldStyle, boolean digestWorkaround)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 
ReproduceRaw(String lineBreak, boolean oldStyle, boolean digestWorkaround)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
verbose()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 
EchoMessageDigest()112         EchoMessageDigest() {
113             super("echo");
114         }
115 
116         @Override
engineReset()117         protected void engineReset() {
118             buf = new ByteArrayOutputStream();
119         }
120 
121         @Override
engineUpdate(byte input)122         protected void engineUpdate(byte input) {
123             buf.write(input);
124         }
125 
126         @Override
engineUpdate(byte[] i, int o, int l)127         protected void engineUpdate(byte[] i, int o, int l) {
128             buf.write(i, o, l);
129         }
130 
engineDigest()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      */
reproduceRawManifest(byte[] mfRawBytes, boolean mainAttsProperlyDelimited, boolean sectionProperlyDelimited)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 
regExscape(String expr)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 
digest(byte[] manifest, String section)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 
test(byte[] originalManifest, boolean mainAttsProperlyDelimited, boolean sectionProperlyDelimited)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 
test(byte[] originalManifest, boolean mainAttsProperlyDelimited)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
testManifestStartsWithBlankLine()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
testEOFAndNoLineBreakAfterMainAttributes()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
testEOFAndNoBlankLineAfterMainAttributes()240     public void testEOFAndNoBlankLineAfterMainAttributes() throws Exception {
241         test(("Manifest-Version: 1.0" + lineBreak).getBytes(UTF_8), false);
242     }
243 
244     @Test
testNormalMainAttributes()245     public void testNormalMainAttributes() throws Exception {
246         test(("Manifest-Version: 1.0" +
247                 lineBreak.repeat(2)).getBytes(UTF_8), true);
248     }
249 
250     @Test
testExtraLineBreakAfterMainAttributes()251     public void testExtraLineBreakAfterMainAttributes() throws Exception {
252         test(("Manifest-Version: 1.0" +
253                 lineBreak.repeat(3)).getBytes(UTF_8), true);
254     }
255 
256     @Test
testIndividualSectionNoLineBreak()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
testIndividualSectionOneLineBreak()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
testNormalIndividualSectionTwoLineBreak()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
testExtraLineBreakAfterIndividualSection()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
testIndividualSections()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
testExtraLineBreakBetweenIndividualSections()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 }
325