File: FindHeaderEndVsManifestDigesterFindFirstSection.java

package info (click to toggle)
openjdk-21 21.0.8%2B9-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 823,976 kB
  • sloc: java: 5,613,338; xml: 1,643,607; cpp: 1,296,296; ansic: 420,291; asm: 404,850; objc: 20,994; sh: 15,271; javascript: 11,245; python: 6,895; makefile: 2,362; perl: 357; awk: 351; sed: 172; jsp: 24; csh: 3
file content (288 lines) | stat: -rw-r--r-- 12,331 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.io.ByteArrayOutputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import sun.security.util.ManifestDigester;

import org.testng.annotations.Test;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.testng.Assert.*;

/**
 * @test
 * @bug 8217375
 * @modules java.base/sun.security.util
 * @run testng FindHeaderEndVsManifestDigesterFindFirstSection
 * @summary Checks that {@link JarSigner#findHeaderEnd} (moved to now
 * {@link #findHeaderEnd} in this test) can be replaced with
 * {@link ManifestDigester#findSection}
 * (first invocation will identify main attributes)
 * without making a difference.
 */
/*
 * Note to future maintainer:
 * While it might look at first glance like this test ensures backwards-
 * compatibility between JarSigner.findHeaderEnd and
 * ManifestDigester.findSection's first invocation that find the main
 * attributes section, at the time of that change, this test continues to
 * verify main attributes digestion now with ManifestDigester.findSection as
 * opposed to previous implementation in JarSigner.findHeaderEnd.
 * Before completely removing this test, make sure that main attributes
 * digestion is covered appropriately with tests. After JarSigner.findHeaderEnd
 * has been removed digests should still continue to match.
 *
 * See also
 * - jdk/test/jdk/sun/security/tools/jarsigner/PreserveRawManifestEntryAndDigest.java
 * for some end-to-end tests utilizing the jarsigner tool,
 * - jdk/test/jdk/sun/security/util/ManifestDigester/FindSection.java and
 * - jdk/test/jdk/sun/security/util/ManifestDigester/DigestInput.java
 * for much more detailed tests at api level
 *
 * Both test mentioned above, however, originally were created when removing
 * confusion of "Manifest-Main-Attributes" individual section with actual main
 * attributes whereas the test here is about changes related to raw manifest
 * reproduction and in the end test pretty much the same behavior.
 */
public class FindHeaderEndVsManifestDigesterFindFirstSection {

    static final boolean FIXED_8217375 = true; // FIXME

    /**
     * from former {@link JarSigner#findHeaderEnd}, subject to verification if
     * it can be replaced with {@link ManifestDigester#findSection}
     */
    @SuppressWarnings("fallthrough")
    private int findHeaderEnd(byte[] bs) {
        // Initial state true to deal with empty header
        boolean newline = true;     // just met a newline
        int len = bs.length;
        for (int i = 0; i < len; i++) {
            switch (bs[i]) {
                case '\r':
                    if (i < len - 1 && bs[i + 1] == '\n') i++;
                    // fallthrough
                case '\n':
                    if (newline) return i + 1;    //+1 to get length
                    newline = true;
                    break;
                default:
                    newline = false;
            }
        }
        // If header end is not found, it means the MANIFEST.MF has only
        // the main attributes section and it does not end with 2 newlines.
        // Returns the whole length so that it can be completely replaced.
        return len;
    }

    @DataProvider(name = "parameters")
    public static Object[][] parameters() {
        List<Object[]> tests = new ArrayList<>();
        for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {
            if ("\r".equals(lineBreak) && !FIXED_8217375) continue;
            for (int numLBs = 0; numLBs <= 3; numLBs++) {
                for (String addSection : new String[] { null, "Ignore" }) {
                    tests.add(new Object[] { lineBreak, numLBs, addSection });
                }
            }
        }
        return tests.toArray(new Object[tests.size()][]);
    }

    @Factory(dataProvider = "parameters")
    public static Object[] createTests(String lineBreak, int numLineBreaks,
            String individualSectionName) {
        return new Object[]{new FindHeaderEndVsManifestDigesterFindFirstSection(
                lineBreak, numLineBreaks, individualSectionName
        )};
    }

    final String lineBreak;
    final int numLineBreaks; // number of line breaks after main attributes
    final String individualSectionName; // null means only main attributes
    final byte[] rawBytes;

    FindHeaderEndVsManifestDigesterFindFirstSection(String lineBreak,
            int numLineBreaks, String individualSectionName) {
        this.lineBreak = lineBreak;
        this.numLineBreaks = numLineBreaks;
        this.individualSectionName = individualSectionName;

        rawBytes = (
            "oldStyle: trailing space " + lineBreak +
            "newStyle: no trailing space" + lineBreak.repeat(numLineBreaks) +
            // numLineBreaks < 2 will not properly delimit individual section
            // but it does not hurt to test that anyway
            (individualSectionName != null ?
                    "Name: " + individualSectionName + lineBreak +
                    "Ignore: nothing here" + lineBreak +
                    lineBreak
                : "")
        ).getBytes(UTF_8);
    }

    @BeforeMethod
    public void verbose() {
        System.out.println("lineBreak = " + stringToIntList(lineBreak));
        System.out.println("numLineBreaks = " + numLineBreaks);
        System.out.println("individualSectionName = " + individualSectionName);
    }

    @FunctionalInterface
    interface Callable {
        void call() throws Exception;
    }

    void catchNoLineBreakAfterMainAttributes(Callable test) throws Exception {
        // manifests cannot be parsed and digested if the main attributes do
        // not end in a blank line (double line break) or one line break
        // immediately before eof.
        boolean failureExpected = numLineBreaks == 0
                && individualSectionName == null;
        try {
            test.call();
            if (failureExpected) fail("expected an exception");
        } catch (NullPointerException | IllegalStateException e) {
            if (!failureExpected) fail("unexpected " + e.getMessage(), e);
        }
    }

    /**
     * Checks that the beginning of the manifest until position<ol>
     * <li>{@code Jarsigner.findHeaderEnd} in the previous version
     * and</li>
     * <li>{@code ManifestDigester.getMainAttsEntry().sections[0].
     * lengthWithBlankLine} in the new version</li>
     * </ol>produce the same offset (TODO: or the same error).
     * The beginning of the manifest until that offset (range
     * <pre>0 .. (offset - 1)</pre>) will be reproduced if the manifest has
     * not changed.
     * <p>
     * Getting {@code startOfNext} of {@link ManifestDigester#findSection}'s
     * first invokation returned {@link ManifestDigester.Position} which
     * identifies the end offset of the main attributes is difficulted by
     * {@link ManifestDigester#findSection} being private and therefore not
     * directly accessible.
     */
    @Test
    public void startOfNextLengthWithBlankLine() throws Exception {
        catchNoLineBreakAfterMainAttributes(() ->
            assertEquals(lengthWithBlankLine(), findHeaderEnd(rawBytes))
        );
    }

    /**
     * Due to its private visibility,
     * {@link ManifestDigester.Section#lengthWithBlankLine} is not directly
     * accessible. However, calling {@link ManifestDigester.Entry#digest}
     * reveals {@code lengthWithBlankLine} as third parameter in
     * <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>
     * on line ManifestDigester.java:212.
     * <p>
     * This value is the same as {@code startOfNext} of
     * {@link ManifestDigester#findSection}'s first invocation returned
     * {@link ManifestDigester.Position} identifying the end offset of the
     * main attributes because<ol>
     * <li>the end offset of the main attributes is assigned to
     * {@code startOfNext} in
     * <pre>pos.startOfNext = i+1;</pre> in ManifestDigester.java:98</li>
     * <li>which is then passed on as the third parameter to the constructor
     * of a new {@link ManifestDigester.Section#Section} by
     * <pre>new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)));</pre>
     * in ManifestDigester.java:128</li>
     * <li>where it is assigned to
     * {@link ManifestDigester.Section#lengthWithBlankLine} by
     * <pre>this.lengthWithBlankLine = lengthWithBlankLine;</pre>
     * in ManifestDigester.java:241</li>
     * <li>from where it is picked up by {@link ManifestDigester.Entry#digest}
     * in
     * <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>
     * in ManifestDigester.java:212</li>
     * </ol>
     * all of which without any modification.
     */
    int lengthWithBlankLine() {
        int[] lengthWithBlankLine = new int[] { 0 };
        new ManifestDigester(rawBytes).get(ManifestDigester.MF_MAIN_ATTRS,
                false).digest(new MessageDigest("lengthWithBlankLine") {
            @Override protected void engineReset() {
                lengthWithBlankLine[0] = 0;
            }
            @Override protected void engineUpdate(byte b) {
                lengthWithBlankLine[0]++;
            }
            @Override protected void engineUpdate(byte[] b, int o, int l) {
                lengthWithBlankLine[0] += l;
            }
            @Override protected byte[] engineDigest() {
                return null;
            }
        });
        return lengthWithBlankLine[0];
    }

    /**
     * Checks that the replacement of {@link JarSigner#findHeaderEnd} is
     * actually used to reproduce manifest main attributes.
     * <p>
     * {@link #startOfNextLengthWithBlankLine} demonstrates that
     * {@link JarSigner#findHeaderEnd} has been replaced successfully with
     * {@link ManifestDigester#findSection} but does not also show that the
     * main attributes are reproduced with the same offset as before.
     * {@link #startOfNextLengthWithBlankLine} uses
     * {@link ManifestDigester.Entry#digest} to demonstrate an equal offset
     * calculated but {@link ManifestDigester.Entry#digest} is not necessarily
     * the same as reproducing, especially when considering
     * {@link ManifestDigester.Entry#oldStyle}.
     */
    @Test(enabled = FIXED_8217375)
    public void reproduceMainAttributes() throws Exception {
        catchNoLineBreakAfterMainAttributes(() -> {
            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            ManifestDigester md = new ManifestDigester(rawBytes);
            // without 8217375 fixed the following line will not even compile
            // so just remove it and skip the test for regression
            md.getMainAttsEntry().reproduceRaw(buf); // FIXME

            assertEquals(buf.size(), findHeaderEnd(rawBytes));
        });
    }

    static List<Integer> stringToIntList(String string) {
        byte[] bytes = string.getBytes(UTF_8);
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < bytes.length; i++) {
            list.add((int) bytes[i]);
        }
        return list;
    }

}