|
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 } |