1 /* |
|
2 * Copyright (c) 1996, 2013, 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. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package sun.tools.jar; |
|
27 |
|
28 import java.io.*; |
|
29 import java.util.*; |
|
30 import java.security.*; |
|
31 |
|
32 import sun.net.www.MessageHeader; |
|
33 import java.util.Base64; |
|
34 |
|
35 |
|
36 import sun.security.pkcs.*; |
|
37 import sun.security.x509.AlgorithmId; |
|
38 |
|
39 /** |
|
40 * <p>A signature file as defined in the <a |
|
41 * href="manifest.html">Manifest and Signature Format</a>. It has |
|
42 * essentially the same structure as a Manifest file in that it is a |
|
43 * set of RFC 822 headers (sections). The first section contains meta |
|
44 * data relevant to the entire file (i.e "Signature-Version:1.0") and |
|
45 * each subsequent section contains data relevant to specific entries: |
|
46 * entry sections. |
|
47 * |
|
48 * <p>Each entry section contains the name of an entry (which must |
|
49 * have a counterpart in the manifest). Like the manifest it contains |
|
50 * a hash, the hash of the manifest section corresponding to the |
|
51 * name. Since the manifest entry contains the hash of the data, this |
|
52 * is equivalent to a signature of the data, plus the attributes of |
|
53 * the manifest entry. |
|
54 * |
|
55 * <p>This signature file format deal with PKCS7 encoded DSA signature |
|
56 * block. It should be straightforward to extent to support other |
|
57 * algorithms. |
|
58 * |
|
59 * @author David Brown |
|
60 * @author Benjamin Renaud */ |
|
61 |
|
62 public class SignatureFile { |
|
63 |
|
64 /* Are we debugging? */ |
|
65 static final boolean debug = false; |
|
66 |
|
67 /* list of headers that all pertain to a particular file in the |
|
68 * archive */ |
|
69 private Vector<MessageHeader> entries = new Vector<>(); |
|
70 |
|
71 /* Right now we only support SHA hashes */ |
|
72 static final String[] hashes = {"SHA"}; |
|
73 |
|
74 static final void debug(String s) { |
|
75 if (debug) |
|
76 System.out.println("sig> " + s); |
|
77 } |
|
78 |
|
79 /* |
|
80 * The manifest we're working with. */ |
|
81 private Manifest manifest; |
|
82 |
|
83 /* |
|
84 * The file name for the file. This is the raw name, i.e. the |
|
85 * extention-less 8 character name (such as MYSIGN) which wil be |
|
86 * used to build the signature filename (MYSIGN.SF) and the block |
|
87 * filename (MYSIGN.DSA) */ |
|
88 private String rawName; |
|
89 |
|
90 /* The digital signature block corresponding to this signature |
|
91 * file. */ |
|
92 private PKCS7 signatureBlock; |
|
93 |
|
94 |
|
95 /** |
|
96 * Private constructor which takes a name a given signature |
|
97 * file. The name must be extension-less and less or equal to 8 |
|
98 * character in length. */ |
|
99 private SignatureFile(String name) throws JarException { |
|
100 |
|
101 entries = new Vector<>(); |
|
102 |
|
103 if (name != null) { |
|
104 if (name.length() > 8 || name.indexOf('.') != -1) { |
|
105 throw new JarException("invalid file name"); |
|
106 } |
|
107 rawName = name.toUpperCase(Locale.ENGLISH); |
|
108 } |
|
109 } |
|
110 |
|
111 /** |
|
112 * Private constructor which takes a name a given signature file |
|
113 * and a new file predicate. If it is a new file, a main header |
|
114 * will be added. */ |
|
115 private SignatureFile(String name, boolean newFile) |
|
116 throws JarException { |
|
117 |
|
118 this(name); |
|
119 |
|
120 if (newFile) { |
|
121 MessageHeader globals = new MessageHeader(); |
|
122 globals.set("Signature-Version", "1.0"); |
|
123 entries.addElement(globals); |
|
124 } |
|
125 } |
|
126 |
|
127 /** |
|
128 * Constructs a new Signature file corresponding to a given |
|
129 * Manifest. All entries in the manifest are signed. |
|
130 * |
|
131 * @param manifest the manifest to use. |
|
132 * |
|
133 * @param name for this signature file. This should |
|
134 * be less than 8 characters, and without a suffix (i.e. |
|
135 * without a period in it. |
|
136 * |
|
137 * @exception JarException if an invalid name is passed in. |
|
138 */ |
|
139 public SignatureFile(Manifest manifest, String name) |
|
140 throws JarException { |
|
141 |
|
142 this(name, true); |
|
143 |
|
144 this.manifest = manifest; |
|
145 Enumeration<MessageHeader> enum_ = manifest.entries(); |
|
146 while (enum_.hasMoreElements()) { |
|
147 MessageHeader mh = enum_.nextElement(); |
|
148 String entryName = mh.findValue("Name"); |
|
149 if (entryName != null) { |
|
150 add(entryName); |
|
151 } |
|
152 } |
|
153 } |
|
154 |
|
155 /** |
|
156 * Constructs a new Signature file corresponding to a given |
|
157 * Manifest. Specific entries in the manifest are signed. |
|
158 * |
|
159 * @param manifest the manifest to use. |
|
160 * |
|
161 * @param entries the entries to sign. |
|
162 * |
|
163 * @param filename for this signature file. This should |
|
164 * be less than 8 characters, and without a suffix (i.e. |
|
165 * without a period in it. |
|
166 * |
|
167 * @exception JarException if an invalid name is passed in. |
|
168 */ |
|
169 public SignatureFile(Manifest manifest, String[] entries, |
|
170 String filename) |
|
171 throws JarException { |
|
172 this(filename, true); |
|
173 this.manifest = manifest; |
|
174 add(entries); |
|
175 } |
|
176 |
|
177 /** |
|
178 * Construct a Signature file from an input stream. |
|
179 * |
|
180 * @exception IOException if an invalid name is passed in or if a |
|
181 * stream exception occurs. |
|
182 */ |
|
183 public SignatureFile(InputStream is, String filename) |
|
184 throws IOException { |
|
185 this(filename); |
|
186 while (is.available() > 0) { |
|
187 MessageHeader m = new MessageHeader(is); |
|
188 entries.addElement(m); |
|
189 } |
|
190 } |
|
191 |
|
192 /** |
|
193 * Construct a Signature file from an input stream. |
|
194 * |
|
195 * @exception IOException if an invalid name is passed in or if a |
|
196 * stream exception occurs. |
|
197 */ |
|
198 public SignatureFile(InputStream is) throws IOException { |
|
199 this(is, null); |
|
200 } |
|
201 |
|
202 public SignatureFile(byte[] bytes) throws IOException { |
|
203 this(new ByteArrayInputStream(bytes)); |
|
204 } |
|
205 |
|
206 /** |
|
207 * Returns the name of the signature file, ending with a ".SF" |
|
208 * suffix */ |
|
209 public String getName() { |
|
210 return "META-INF/" + rawName + ".SF"; |
|
211 } |
|
212 |
|
213 /** |
|
214 * Returns the name of the block file, ending with a block suffix |
|
215 * such as ".DSA". */ |
|
216 public String getBlockName() { |
|
217 String suffix = "DSA"; |
|
218 if (signatureBlock != null) { |
|
219 SignerInfo info = signatureBlock.getSignerInfos()[0]; |
|
220 suffix = info.getDigestEncryptionAlgorithmId().getName(); |
|
221 String temp = AlgorithmId.getEncAlgFromSigAlg(suffix); |
|
222 if (temp != null) suffix = temp; |
|
223 } |
|
224 return "META-INF/" + rawName + "." + suffix; |
|
225 } |
|
226 |
|
227 /** |
|
228 * Returns the signature block associated with this file. |
|
229 */ |
|
230 public PKCS7 getBlock() { |
|
231 return signatureBlock; |
|
232 } |
|
233 |
|
234 /** |
|
235 * Sets the signature block associated with this file. |
|
236 */ |
|
237 public void setBlock(PKCS7 block) { |
|
238 this.signatureBlock = block; |
|
239 } |
|
240 |
|
241 /** |
|
242 * Add a set of entries from the current manifest. |
|
243 */ |
|
244 public void add(String[] entries) throws JarException { |
|
245 for (int i = 0; i < entries.length; i++) { |
|
246 add (entries[i]); |
|
247 } |
|
248 } |
|
249 |
|
250 /** |
|
251 * Add a specific entry from the current manifest. |
|
252 */ |
|
253 public void add(String entry) throws JarException { |
|
254 MessageHeader mh = manifest.getEntry(entry); |
|
255 if (mh == null) { |
|
256 throw new JarException("entry " + entry + " not in manifest"); |
|
257 } |
|
258 MessageHeader smh; |
|
259 try { |
|
260 smh = computeEntry(mh); |
|
261 } catch (IOException e) { |
|
262 throw new JarException(e.getMessage()); |
|
263 } |
|
264 entries.addElement(smh); |
|
265 } |
|
266 |
|
267 /** |
|
268 * Get the entry corresponding to a given name. Returns null if |
|
269 *the entry does not exist. |
|
270 */ |
|
271 public MessageHeader getEntry(String name) { |
|
272 Enumeration<MessageHeader> enum_ = entries(); |
|
273 while(enum_.hasMoreElements()) { |
|
274 MessageHeader mh = enum_.nextElement(); |
|
275 if (name.equals(mh.findValue("Name"))) { |
|
276 return mh; |
|
277 } |
|
278 } |
|
279 return null; |
|
280 } |
|
281 |
|
282 /** |
|
283 * Returns the n-th entry. The global header is a entry 0. */ |
|
284 public MessageHeader entryAt(int n) { |
|
285 return entries.elementAt(n); |
|
286 } |
|
287 |
|
288 /** |
|
289 * Returns an enumeration of the entries. |
|
290 */ |
|
291 public Enumeration<MessageHeader> entries() { |
|
292 return entries.elements(); |
|
293 } |
|
294 |
|
295 /** |
|
296 * Given a manifest entry, computes the signature entry for this |
|
297 * manifest entry. |
|
298 */ |
|
299 private MessageHeader computeEntry(MessageHeader mh) throws IOException { |
|
300 MessageHeader smh = new MessageHeader(); |
|
301 |
|
302 String name = mh.findValue("Name"); |
|
303 if (name == null) { |
|
304 return null; |
|
305 } |
|
306 smh.set("Name", name); |
|
307 |
|
308 try { |
|
309 for (int i = 0; i < hashes.length; ++i) { |
|
310 MessageDigest dig = getDigest(hashes[i]); |
|
311 ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
312 PrintStream ps = new PrintStream(baos); |
|
313 mh.print(ps); |
|
314 byte[] headerBytes = baos.toByteArray(); |
|
315 byte[] digest = dig.digest(headerBytes); |
|
316 smh.set(hashes[i] + "-Digest", Base64.getMimeEncoder().encodeToString(digest)); |
|
317 } |
|
318 return smh; |
|
319 } catch (NoSuchAlgorithmException e) { |
|
320 throw new JarException(e.getMessage()); |
|
321 } |
|
322 } |
|
323 |
|
324 private Hashtable<String, MessageDigest> digests = new Hashtable<>(); |
|
325 |
|
326 private MessageDigest getDigest(String algorithm) |
|
327 throws NoSuchAlgorithmException { |
|
328 MessageDigest dig = digests.get(algorithm); |
|
329 if (dig == null) { |
|
330 dig = MessageDigest.getInstance(algorithm); |
|
331 digests.put(algorithm, dig); |
|
332 } |
|
333 dig.reset(); |
|
334 return dig; |
|
335 } |
|
336 |
|
337 |
|
338 /** |
|
339 * Add a signature file at current position in a stream |
|
340 */ |
|
341 public void stream(OutputStream os) throws IOException { |
|
342 |
|
343 /* the first header in the file should be the global one. |
|
344 * It should say "SignatureFile-Version: x.x"; barf if not |
|
345 */ |
|
346 MessageHeader globals = entries.elementAt(0); |
|
347 if (globals.findValue("Signature-Version") == null) { |
|
348 throw new JarException("Signature file requires " + |
|
349 "Signature-Version: 1.0 in 1st header"); |
|
350 } |
|
351 |
|
352 PrintStream ps = new PrintStream(os); |
|
353 globals.print(ps); |
|
354 |
|
355 for (int i = 1; i < entries.size(); ++i) { |
|
356 MessageHeader mh = entries.elementAt(i); |
|
357 mh.print(ps); |
|
358 } |
|
359 } |
|
360 } |
|