1 /* |
1 /* |
2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. |
2 * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. |
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 * |
4 * |
5 * This code is free software; you can redistribute it and/or modify it |
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 |
6 * under the terms of the GNU General Public License version 2 only, as |
7 * published by the Free Software Foundation. Oracle designates this |
7 * published by the Free Software Foundation. Oracle designates this |
23 * questions. |
23 * questions. |
24 */ |
24 */ |
25 |
25 |
26 package sun.security.util; |
26 package sun.security.util; |
27 |
27 |
28 import java.security.*; |
28 import java.security.MessageDigest; |
29 import java.util.ArrayList; |
29 import java.util.ArrayList; |
30 import java.util.HashMap; |
30 import java.util.HashMap; |
31 import java.io.ByteArrayOutputStream; |
31 import java.io.ByteArrayOutputStream; |
|
32 import java.io.OutputStream; |
|
33 import java.io.IOException; |
32 import java.util.List; |
34 import java.util.List; |
33 |
35 |
34 import static java.nio.charset.StandardCharsets.UTF_8; |
36 import static java.nio.charset.StandardCharsets.UTF_8; |
35 |
37 |
36 /** |
38 /** |
38 * Please note that multiple sections might have the same name, and they |
40 * Please note that multiple sections might have the same name, and they |
39 * all belong to a single Entry. |
41 * all belong to a single Entry. |
40 */ |
42 */ |
41 public class ManifestDigester { |
43 public class ManifestDigester { |
42 |
44 |
|
45 /** |
|
46 * The part "{@code Manifest-Main-Attributes}" of the main attributes |
|
47 * digest header name in a signature file as described in the jar |
|
48 * specification: |
|
49 * <blockquote>{@code x-Digest-Manifest-Main-Attributes} |
|
50 * (where x is the standard name of a {@link MessageDigest} algorithm): |
|
51 * The value of this attribute is the digest value of the main attributes |
|
52 * of the manifest.</blockquote> |
|
53 * @see <a href="{@docRoot}/../specs/jar/jar.html#signature-file"> |
|
54 * JAR File Specification, section Signature File</a> |
|
55 * @see #getMainAttsEntry |
|
56 */ |
43 public static final String MF_MAIN_ATTRS = "Manifest-Main-Attributes"; |
57 public static final String MF_MAIN_ATTRS = "Manifest-Main-Attributes"; |
44 |
58 |
45 /** the raw bytes of the manifest */ |
59 /** the raw bytes of the manifest */ |
46 private byte[] rawBytes; |
60 private final byte[] rawBytes; |
47 |
61 |
48 /** the entries grouped by names */ |
62 private final Entry mainAttsEntry; |
49 private HashMap<String, Entry> entries; // key is a UTF-8 string |
63 |
|
64 /** individual sections by their names */ |
|
65 private final HashMap<String, Entry> entries = new HashMap<>(); |
50 |
66 |
51 /** state returned by findSection */ |
67 /** state returned by findSection */ |
52 static class Position { |
68 static class Position { |
53 int endOfFirstLine; // not including newline character |
69 int endOfFirstLine; // not including newline character |
54 |
70 |
70 */ |
86 */ |
71 @SuppressWarnings("fallthrough") |
87 @SuppressWarnings("fallthrough") |
72 private boolean findSection(int offset, Position pos) |
88 private boolean findSection(int offset, Position pos) |
73 { |
89 { |
74 int i = offset, len = rawBytes.length; |
90 int i = offset, len = rawBytes.length; |
75 int last = offset; |
91 int last = offset - 1; |
76 int next; |
92 int next; |
77 boolean allBlank = true; |
93 boolean allBlank = true; |
78 |
94 |
79 pos.endOfFirstLine = -1; |
95 /* denotes that a position is not yet assigned. |
|
96 * As a primitive type int it cannot be null |
|
97 * and -1 would be confused with (i - 1) when i == 0 */ |
|
98 final int UNASSIGNED = Integer.MIN_VALUE; |
|
99 |
|
100 pos.endOfFirstLine = UNASSIGNED; |
80 |
101 |
81 while (i < len) { |
102 while (i < len) { |
82 byte b = rawBytes[i]; |
103 byte b = rawBytes[i]; |
83 switch(b) { |
104 switch(b) { |
84 case '\r': |
105 case '\r': |
85 if (pos.endOfFirstLine == -1) |
106 if (pos.endOfFirstLine == UNASSIGNED) |
86 pos.endOfFirstLine = i-1; |
107 pos.endOfFirstLine = i-1; |
87 if ((i < len) && (rawBytes[i+1] == '\n')) |
108 if (i < len - 1 && rawBytes[i + 1] == '\n') |
88 i++; |
109 i++; |
89 /* fall through */ |
110 /* fall through */ |
90 case '\n': |
111 case '\n': |
91 if (pos.endOfFirstLine == -1) |
112 if (pos.endOfFirstLine == UNASSIGNED) |
92 pos.endOfFirstLine = i-1; |
113 pos.endOfFirstLine = i-1; |
93 if (allBlank || (i == len-1)) { |
114 if (allBlank || (i == len-1)) { |
94 if (i == len-1) |
115 pos.endOfSection = allBlank ? last : i; |
95 pos.endOfSection = i; |
|
96 else |
|
97 pos.endOfSection = last; |
|
98 pos.startOfNext = i+1; |
116 pos.startOfNext = i+1; |
99 return true; |
117 return true; |
100 } |
118 } |
101 else { |
119 else { |
102 // start of a new line |
120 // start of a new line |
114 } |
132 } |
115 |
133 |
116 public ManifestDigester(byte[] bytes) |
134 public ManifestDigester(byte[] bytes) |
117 { |
135 { |
118 rawBytes = bytes; |
136 rawBytes = bytes; |
119 entries = new HashMap<>(); |
|
120 |
137 |
121 Position pos = new Position(); |
138 Position pos = new Position(); |
122 |
139 |
123 if (!findSection(0, pos)) |
140 if (!findSection(0, pos)) { |
|
141 mainAttsEntry = null; |
124 return; // XXX: exception? |
142 return; // XXX: exception? |
|
143 } |
125 |
144 |
126 // create an entry for main attributes |
145 // create an entry for main attributes |
127 entries.put(MF_MAIN_ATTRS, new Entry().addSection( |
146 mainAttsEntry = new Entry().addSection(new Section( |
128 new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes))); |
147 0, pos.endOfSection + 1, pos.startOfNext, rawBytes)); |
129 |
148 |
130 int start = pos.startOfNext; |
149 int start = pos.startOfNext; |
131 while(findSection(start, pos)) { |
150 while(findSection(start, pos)) { |
132 int len = pos.endOfFirstLine-start+1; |
151 int len = pos.endOfFirstLine-start+1; |
133 int sectionLen = pos.endOfSection-start+1; |
152 int sectionLen = pos.endOfSection-start+1; |
134 int sectionLenWithBlank = pos.startOfNext-start; |
153 int sectionLenWithBlank = pos.startOfNext-start; |
135 |
154 |
136 if (len > 6) { |
155 if (len >= 6) { // 6 == "Name: ".length() |
137 if (isNameAttr(bytes, start)) { |
156 if (isNameAttr(bytes, start)) { |
138 ByteArrayOutputStream nameBuf = new ByteArrayOutputStream(); |
157 ByteArrayOutputStream nameBuf = new ByteArrayOutputStream(); |
139 nameBuf.write(bytes, start+6, len-6); |
158 nameBuf.write(bytes, start+6, len-6); |
140 |
159 |
141 int i = start + len; |
160 int i = start + len; |
142 if ((i-start) < sectionLen) { |
161 if ((i-start) < sectionLen) { |
143 if (bytes[i] == '\r') { |
162 if (bytes[i] == '\r' |
|
163 && i + 1 - start < sectionLen |
|
164 && bytes[i + 1] == '\n') { |
144 i += 2; |
165 i += 2; |
145 } else { |
166 } else { |
146 i += 1; |
167 i += 1; |
147 } |
168 } |
148 } |
169 } |
150 while ((i-start) < sectionLen) { |
171 while ((i-start) < sectionLen) { |
151 if (bytes[i++] == ' ') { |
172 if (bytes[i++] == ' ') { |
152 // name is wrapped |
173 // name is wrapped |
153 int wrapStart = i; |
174 int wrapStart = i; |
154 while (((i-start) < sectionLen) |
175 while (((i-start) < sectionLen) |
155 && (bytes[i++] != '\n')); |
176 && (bytes[i] != '\r') |
156 if (bytes[i-1] != '\n') |
177 && (bytes[i] != '\n')) i++; |
157 return; // XXX: exception? |
178 int wrapLen = i - wrapStart; |
158 int wrapLen; |
179 if (i - start < sectionLen) { |
159 if (bytes[i-2] == '\r') |
180 i++; |
160 wrapLen = i-wrapStart-2; |
181 if (bytes[i - 1] == '\r' |
161 else |
182 && i - start < sectionLen |
162 wrapLen = i-wrapStart-1; |
183 && bytes[i] == '\n') |
|
184 i++; |
|
185 } |
163 |
186 |
164 nameBuf.write(bytes, wrapStart, wrapLen); |
187 nameBuf.write(bytes, wrapStart, wrapLen); |
165 } else { |
188 } else { |
166 break; |
189 break; |
167 } |
190 } |
168 } |
191 } |
169 |
192 |
170 entries.computeIfAbsent(new String(nameBuf.toByteArray(), UTF_8), |
193 entries.computeIfAbsent(nameBuf.toString(UTF_8), |
171 dummy -> new Entry()) |
194 dummy -> new Entry()) |
172 .addSection(new Section(start, sectionLen, |
195 .addSection(new Section(start, sectionLen, |
173 sectionLenWithBlank, rawBytes)); |
196 sectionLenWithBlank, rawBytes)); |
174 } |
197 } |
175 } |
198 } |
200 { |
223 { |
201 sections.add(sec); |
224 sections.add(sec); |
202 return this; |
225 return this; |
203 } |
226 } |
204 |
227 |
|
228 /** |
|
229 * Check if the sections (particularly the last one of usually only one) |
|
230 * are properly delimited with a trailing blank line so that another |
|
231 * section can be correctly appended and return {@code true} or return |
|
232 * {@code false} to indicate that reproduction is not advised and should |
|
233 * be carried out with a clean "normalized" newly-written manifest. |
|
234 * |
|
235 * @see #reproduceRaw |
|
236 */ |
|
237 public boolean isProperlyDelimited() { |
|
238 return sections.stream().allMatch( |
|
239 Section::isProperlySectionDelimited); |
|
240 } |
|
241 |
|
242 public void reproduceRaw(OutputStream out) throws IOException { |
|
243 for (Section sec : sections) { |
|
244 out.write(sec.rawBytes, sec.offset, sec.lengthWithBlankLine); |
|
245 } |
|
246 } |
|
247 |
205 public byte[] digest(MessageDigest md) |
248 public byte[] digest(MessageDigest md) |
206 { |
249 { |
207 md.reset(); |
250 md.reset(); |
208 for (Section sec : sections) { |
251 for (Section sec : sections) { |
209 if (oldStyle) { |
252 if (oldStyle) { |
238 { |
281 { |
239 this.offset = offset; |
282 this.offset = offset; |
240 this.length = length; |
283 this.length = length; |
241 this.lengthWithBlankLine = lengthWithBlankLine; |
284 this.lengthWithBlankLine = lengthWithBlankLine; |
242 this.rawBytes = rawBytes; |
285 this.rawBytes = rawBytes; |
|
286 } |
|
287 |
|
288 /** |
|
289 * Returns {@code true} if the raw section is terminated with a blank |
|
290 * line so that another section can possibly be appended resulting in a |
|
291 * valid manifest and {@code false} otherwise. |
|
292 */ |
|
293 private boolean isProperlySectionDelimited() { |
|
294 return lengthWithBlankLine > length; |
243 } |
295 } |
244 |
296 |
245 private static void doOldStyle(MessageDigest md, |
297 private static void doOldStyle(MessageDigest md, |
246 byte[] bytes, |
298 byte[] bytes, |
247 int offset, |
299 int offset, |
266 } |
318 } |
267 md.update(bytes, start, i-start); |
319 md.update(bytes, start, i-start); |
268 } |
320 } |
269 } |
321 } |
270 |
322 |
|
323 /** |
|
324 * @see #MF_MAIN_ATTRS |
|
325 */ |
|
326 public Entry getMainAttsEntry() { |
|
327 return mainAttsEntry; |
|
328 } |
|
329 |
|
330 /** |
|
331 * @see #MF_MAIN_ATTRS |
|
332 */ |
|
333 public Entry getMainAttsEntry(boolean oldStyle) { |
|
334 mainAttsEntry.oldStyle = oldStyle; |
|
335 return mainAttsEntry; |
|
336 } |
|
337 |
|
338 public Entry get(String name) { |
|
339 return entries.get(name); |
|
340 } |
|
341 |
271 public Entry get(String name, boolean oldStyle) { |
342 public Entry get(String name, boolean oldStyle) { |
272 Entry e = entries.get(name); |
343 Entry e = get(name); |
273 if (e != null) |
344 if (e == null && MF_MAIN_ATTRS.equals(name)) { |
|
345 e = getMainAttsEntry(); |
|
346 } |
|
347 if (e != null) { |
274 e.oldStyle = oldStyle; |
348 e.oldStyle = oldStyle; |
|
349 } |
275 return e; |
350 return e; |
276 } |
351 } |
277 |
352 |
278 public byte[] manifestDigest(MessageDigest md) { |
353 public byte[] manifestDigest(MessageDigest md) { |
279 md.reset(); |
354 md.reset(); |