001: /*
002: * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.tools.jar;
027:
028: import java.io.*;
029: import java.util.*;
030: import java.security.*;
031:
032: import sun.net.www.MessageHeader;
033: import sun.misc.BASE64Encoder;
034: import sun.misc.BASE64Decoder;
035:
036: import sun.security.pkcs.*;
037:
038: /**
039: * <p>A signature file as defined in the <a
040: * href="manifest.html">Manifest and Signature Format</a>. It has
041: * essentially the same structure as a Manifest file in that it is a
042: * set of RFC 822 headers (sections). The first section contains meta
043: * data relevant to the entire file (i.e "Signature-Version:1.0") and
044: * each subsequent section contains data relevant to specific entries:
045: * entry sections.
046: *
047: * <p>Each entry section contains the name of an entry (which must
048: * have a counterpart in the manifest). Like the manifest it contains
049: * a hash, the hash of the manifest section correspondind to the
050: * name. Since the manifest entry contains the hash of the data, this
051: * is equivalent to a signature of the data, plus the attributes of
052: * the manifest entry.
053: *
054: * <p>This signature file format deal with PKCS7 encoded DSA signature
055: * block. It should be straightforward to extent to support other
056: * algorithms.
057: *
058: * @version 1.35 05/05/07
059: * @author David Brown
060: * @author Benjamin Renaud */
061:
062: public class SignatureFile {
063:
064: /* Are we debugging? */
065: static final boolean debug = false;
066:
067: /* list of headers that all pertain to a particular file in the
068: * archive */
069: private Vector entries = new Vector();
070:
071: /* Right now we only support SHA hashes */
072: static final String[] hashes = { "SHA" };
073:
074: static final void debug(String s) {
075: if (debug)
076: System.out.println("sig> " + s);
077: }
078:
079: /*
080: * The manifest we're working with. */
081: private Manifest manifest;
082:
083: /*
084: * The file name for the file. This is the raw name, i.e. the
085: * extention-less 8 character name (such as MYSIGN) which wil be
086: * used to build the signature filename (MYSIGN.SF) and the block
087: * filename (MYSIGN.DSA) */
088: private String rawName;
089:
090: /* The digital signature block corresponding to this signature
091: * file. */
092: private PKCS7 signatureBlock;
093:
094: /**
095: * Private constructor which takes a name a given signature
096: * file. The name must be extension-less and less or equal to 8
097: * character in length. */
098: private SignatureFile(String name) throws JarException {
099:
100: entries = new Vector();
101:
102: if (name != null) {
103: if (name.length() > 8 || name.indexOf('.') != -1) {
104: throw new JarException("invalid file name");
105: }
106: rawName = name.toUpperCase();
107: }
108: }
109:
110: /**
111: * Private constructor which takes a name a given signature file
112: * and a new file predicate. If it is a new file, a main header
113: * will be added. */
114: private SignatureFile(String name, boolean newFile)
115: throws JarException {
116:
117: this (name);
118:
119: if (newFile) {
120: MessageHeader globals = new MessageHeader();
121: globals.set("Signature-Version", "1.0");
122: entries.addElement(globals);
123: }
124: }
125:
126: /**
127: * Constructs a new Signature file corresponding to a given
128: * Manifest. All entries in the manifest are signed.
129: *
130: * @param manifest the manifest to use.
131: *
132: * @param name for this signature file. This should
133: * be less than 8 characters, and without a suffix (i.e.
134: * without a period in it.
135: *
136: * @exception JarException if an invalid name is passed in.
137: */
138: public SignatureFile(Manifest manifest, String name)
139: throws JarException {
140:
141: this (name, true);
142:
143: this .manifest = manifest;
144: Enumeration enum_ = manifest.entries();
145: while (enum_.hasMoreElements()) {
146: MessageHeader mh = (MessageHeader) enum_.nextElement();
147: String entryName = mh.findValue("Name");
148: if (entryName != null) {
149: add(entryName);
150: }
151: }
152: }
153:
154: /**
155: * Constructs a new Signature file corresponding to a given
156: * Manifest. Specific entries in the manifest are signed.
157: *
158: * @param manifest the manifest to use.
159: *
160: * @param entries the entries to sign.
161: *
162: * @param filename for this signature file. This should
163: * be less than 8 characters, and without a suffix (i.e.
164: * without a period in it.
165: *
166: * @exception JarException if an invalid name is passed in.
167: */
168: public SignatureFile(Manifest manifest, String[] entries,
169: String filename) throws JarException {
170: this (filename, true);
171: this .manifest = manifest;
172: add(entries);
173: }
174:
175: /**
176: * Construct a Signature file from an input stream.
177: *
178: * @exception IOException if an invalid name is passed in or if a
179: * stream exception occurs.
180: */
181: public SignatureFile(InputStream is, String filename)
182: throws IOException {
183: this (filename);
184: while (is.available() > 0) {
185: MessageHeader m = new MessageHeader(is);
186: entries.addElement(m);
187: }
188: }
189:
190: /**
191: * Construct a Signature file from an input stream.
192: *
193: * @exception IOException if an invalid name is passed in or if a
194: * stream exception occurs.
195: */
196: public SignatureFile(InputStream is) throws IOException {
197: this (is, null);
198: }
199:
200: public SignatureFile(byte[] bytes) throws IOException {
201: this (new ByteArrayInputStream(bytes));
202: }
203:
204: /**
205: * Returns the name of the signature file, ending with a ".SF"
206: * suffix */
207: public String getName() {
208: return "META-INF/" + rawName + ".SF";
209: }
210:
211: /**
212: * Returns the name of the block file, ending with a block suffix
213: * such as ".DSA". */
214: public String getBlockName() {
215: String suffix = "DSA";
216: if (signatureBlock != null) {
217: SignerInfo info = signatureBlock.getSignerInfos()[0];
218: suffix = info.getDigestEncryptionAlgorithmId().getName();
219: suffix = suffix.substring(suffix.length() - 3);
220: }
221: return "META-INF/" + rawName + "." + suffix;
222: }
223:
224: /**
225: * Returns the signature block associated with this file.
226: */
227: public PKCS7 getBlock() {
228: return signatureBlock;
229: }
230:
231: /**
232: * Sets the signature block associated with this file.
233: */
234: public void setBlock(PKCS7 block) {
235: this .signatureBlock = block;
236: }
237:
238: /**
239: * Add a set of entries from the current manifest.
240: */
241: public void add(String[] entries) throws JarException {
242: for (int i = 0; i < entries.length; i++) {
243: add(entries[i]);
244: }
245: }
246:
247: /**
248: * Add a specific entry from the current manifest.
249: */
250: public void add(String entry) throws JarException {
251: MessageHeader mh = manifest.getEntry(entry);
252: if (mh == null) {
253: throw new JarException("entry " + entry
254: + " not in manifest");
255: }
256: MessageHeader smh;
257: try {
258: smh = computeEntry(mh);
259: } catch (IOException e) {
260: throw new JarException(e.getMessage());
261: }
262: entries.addElement(smh);
263: }
264:
265: /**
266: * Get the entry corresponding to a given name. Returns null if
267: *the entry does not exist.
268: */
269: public MessageHeader getEntry(String name) {
270: Enumeration enum_ = entries();
271: while (enum_.hasMoreElements()) {
272: MessageHeader mh = (MessageHeader) enum_.nextElement();
273: if (name.equals(mh.findValue("Name"))) {
274: return mh;
275: }
276: }
277: return null;
278: }
279:
280: /**
281: * Returns the n-th entry. The global header is a entry 0. */
282: public MessageHeader entryAt(int n) {
283: return (MessageHeader) entries.elementAt(n);
284: }
285:
286: /**
287: * Returns an enumeration of the entries.
288: */
289: public Enumeration entries() {
290: return entries.elements();
291: }
292:
293: /**
294: * Given a manifest entry, computes the signature entry for this
295: * manifest entry.
296: */
297: private MessageHeader computeEntry(MessageHeader mh)
298: throws IOException {
299: MessageHeader smh = new MessageHeader();
300:
301: String name = mh.findValue("Name");
302: if (name == null) {
303: return null;
304: }
305: smh.set("Name", name);
306:
307: BASE64Encoder encoder = new BASE64Encoder();
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", encoder.encode(digest));
317: }
318: return smh;
319: } catch (NoSuchAlgorithmException e) {
320: throw new JarException(e.getMessage());
321: }
322: }
323:
324: private Hashtable digests = new Hashtable();
325:
326: private MessageDigest getDigest(String algorithm)
327: throws NoSuchAlgorithmException {
328: MessageDigest dig = (MessageDigest) 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: * Add a signature file at current position in a stream
339: */
340: public void stream(OutputStream os) throws IOException {
341:
342: /* the first header in the file should be the global one.
343: * It should say "SignatureFile-Version: x.x"; barf if not
344: */
345: MessageHeader globals = (MessageHeader) entries.elementAt(0);
346: if (globals.findValue("Signature-Version") == null) {
347: throw new JarException("Signature file requires "
348: + "Signature-Version: 1.0 in 1st header");
349: }
350:
351: PrintStream ps = new PrintStream(os);
352: globals.print(ps);
353:
354: for (int i = 1; i < entries.size(); ++i) {
355: MessageHeader mh = (MessageHeader) entries.elementAt(i);
356: mh.print(ps);
357: }
358: }
359: }
|