001: /*
002: * @(#)SignatureFileVerifier.java 1.29 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package sun.security.util;
029:
030: import java.security.cert.Certificate;
031: import java.security.cert.X509Certificate;
032: import java.security.cert.CertificateException;
033: import java.security.*;
034: import java.io.*;
035: import java.util.*;
036: import java.util.jar.*;
037:
038: import sun.security.pkcs.*;
039: import sun.misc.BASE64Decoder;
040:
041: public class SignatureFileVerifier {
042:
043: /* Are we debugging ? */
044: private static final Debug debug = Debug.getInstance("jar");
045:
046: /* cache of Certificate[] objects */
047: private ArrayList certCache;
048:
049: /** the PKCS7 block for this .DSA/.RSA file */
050: private PKCS7 block;
051:
052: /** the raw bytes of the .SF file */
053: private byte sfBytes[];
054:
055: /** the name of the signature block file, uppercased and without
056: * the extension (.DSA/.RSA)
057: */
058: private String name;
059:
060: /** the ManifestDigester */
061: private ManifestDigester md;
062:
063: /** cache of created MessageDigest objects */
064: private HashMap createdDigests;
065:
066: /* workaround for parsing Netscape jars */
067: private boolean workaround = false;
068:
069: /**
070: * Create the named SignatureFileVerifier.
071: *
072: * @param name the name of the signature block file (.DSA/.RSA)
073: *
074: * @param rawBytes the raw bytes of the signature block file
075: */
076: public SignatureFileVerifier(ArrayList certCache,
077: ManifestDigester md, String name, byte rawBytes[])
078: throws IOException {
079: block = new PKCS7(rawBytes);
080: sfBytes = block.getContentInfo().getData();
081: this .name = name.substring(0, name.lastIndexOf("."))
082: .toUpperCase(Locale.ENGLISH);
083: this .md = md;
084: this .certCache = certCache;
085: }
086:
087: /**
088: * returns true if we need the .SF file
089: */
090: public boolean needSignatureFileBytes() {
091:
092: return sfBytes == null;
093: }
094:
095: /**
096: * returns true if we need this .SF file.
097: *
098: * @param name the name of the .SF file without the extension
099: *
100: */
101: public boolean needSignatureFile(String name) {
102: return this .name.equalsIgnoreCase(name);
103: }
104:
105: /**
106: * used to set the raw bytes of the .SF file when it
107: * is external to the signature block file.
108: */
109: public void setSignatureFile(byte sfBytes[]) {
110: this .sfBytes = sfBytes;
111: }
112:
113: /** get digest from cache */
114:
115: private MessageDigest getDigest(String algorithm) {
116: if (createdDigests == null)
117: createdDigests = new HashMap();
118:
119: MessageDigest digest = (MessageDigest) createdDigests
120: .get(algorithm);
121:
122: if (digest == null) {
123: try {
124: digest = MessageDigest.getInstance(algorithm);
125: createdDigests.put(algorithm, digest);
126: } catch (NoSuchAlgorithmException nsae) {
127: // ignore
128: }
129: }
130: return digest;
131: }
132:
133: /**
134: * process the signature block file. Goes through the .SF file
135: * and adds certificates for each section where the .SF section
136: * hash was verified against the Manifest section.
137: *
138: *
139: */
140: public void process(Hashtable certificates) throws IOException,
141: SignatureException, NoSuchAlgorithmException, JarException {
142: Manifest sf = new Manifest();
143: sf.read(new ByteArrayInputStream(sfBytes));
144:
145: String version = sf.getMainAttributes().getValue(
146: Attributes.Name.SIGNATURE_VERSION);
147:
148: if ((version == null) || !(version.equalsIgnoreCase("1.0"))) {
149: // FIXME: should this be an exception?
150: // for now we just ignore this signature file
151: return;
152: }
153:
154: SignerInfo[] infos = block.verify(sfBytes);
155:
156: if (infos == null) {
157: throw new SecurityException(
158: "cannot verify signature block file " + name);
159: }
160:
161: BASE64Decoder decoder = new BASE64Decoder();
162:
163: Certificate[] newCerts = getCertificates(infos, block);
164:
165: // make sure we have something to do all this work for...
166: if (newCerts == null)
167: return;
168:
169: Iterator entries = sf.getEntries().entrySet().iterator();
170:
171: // see if we can verify the whole manifest first
172: boolean manifestSigned = verifyManifestHash(sf, md, decoder);
173:
174: // go through each section in the signature file
175: while (entries.hasNext()) {
176:
177: Map.Entry e = (Map.Entry) entries.next();
178: String name = (String) e.getKey();
179:
180: if (manifestSigned
181: || (verifySection((Attributes) e.getValue(), name,
182: md, decoder))) {
183:
184: if (name.startsWith("./"))
185: name = name.substring(2);
186:
187: if (name.startsWith("/"))
188: name = name.substring(1);
189:
190: updateCerts(newCerts, certificates, name);
191:
192: if (debug != null) {
193: debug.println("processSignature signed name = "
194: + name);
195: }
196:
197: } else if (debug != null) {
198: debug.println("processSignature unsigned name = "
199: + name);
200: }
201:
202: }
203: }
204:
205: /**
206: * See if the whole manifest was signed.
207: */
208: private boolean verifyManifestHash(Manifest sf,
209: ManifestDigester md, BASE64Decoder decoder)
210: throws IOException {
211: Attributes mattr = sf.getMainAttributes();
212: boolean manifestSigned = false;
213: Iterator mit = mattr.entrySet().iterator();
214:
215: // go through all the attributes and process *-Digest-Manifest entries
216: while (mit.hasNext()) {
217: Map.Entry se = (Map.Entry) mit.next();
218: String key = se.getKey().toString();
219:
220: if (key.toUpperCase(Locale.ENGLISH).endsWith(
221: "-DIGEST-MANIFEST")) {
222: // 16 is length of "-Digest-Manifest"
223: String algorithm = key.substring(0, key.length() - 16);
224:
225: MessageDigest digest = getDigest(algorithm);
226: if (digest != null) {
227: byte[] computedHash = md.manifestDigest(digest);
228: byte[] expectedHash = decoder
229: .decodeBuffer((String) se.getValue());
230:
231: if (debug != null) {
232: debug
233: .println("Signature File: Manifest digest "
234: + digest.getAlgorithm());
235: debug.println(" sigfile "
236: + toHex(expectedHash));
237: debug.println(" computed "
238: + toHex(computedHash));
239: debug.println();
240: }
241:
242: if (MessageDigest.isEqual(computedHash,
243: expectedHash)) {
244: manifestSigned = true;
245: } else {
246: //we will continue and verify each section
247: }
248: }
249: }
250: }
251: return manifestSigned;
252: }
253:
254: /**
255: * given the .SF digest header, and the data from the
256: * section in the manifest, see if the hashes match.
257: * if not, throw a SecurityException.
258: *
259: * @return true if all the -Digest headers verified
260: * @exception SecurityException if the hash was not equal
261: */
262:
263: private boolean verifySection(Attributes sfAttr, String name,
264: ManifestDigester md, BASE64Decoder decoder)
265: throws IOException {
266: boolean oneDigestVerified = false;
267: ManifestDigester.Entry mde = md.get(name, block.isOldStyle());
268:
269: if (mde == null) {
270: throw new SecurityException(
271: "no manifiest section for signature file entry "
272: + name);
273: }
274:
275: if (sfAttr != null) {
276:
277: //sun.misc.HexDumpEncoder hex = new sun.misc.HexDumpEncoder();
278: //hex.encodeBuffer(data, System.out);
279:
280: Iterator it = sfAttr.entrySet().iterator();
281:
282: // go through all the attributes and process *-Digest entries
283: while (it.hasNext()) {
284: Map.Entry se = (Map.Entry) it.next();
285: String key = se.getKey().toString();
286:
287: if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
288: // 7 is length of "-Digest"
289: String algorithm = key.substring(0,
290: key.length() - 7);
291:
292: MessageDigest digest = getDigest(algorithm);
293:
294: if (digest != null) {
295: boolean ok = false;
296:
297: byte[] expected = decoder
298: .decodeBuffer((String) se.getValue());
299: byte[] computed;
300: if (workaround) {
301: computed = mde.digestWorkaround(digest);
302: } else {
303: computed = mde.digest(digest);
304: }
305:
306: if (debug != null) {
307: debug.println("Signature Block File: "
308: + name + " digest="
309: + digest.getAlgorithm());
310: debug.println(" expected "
311: + toHex(expected));
312: debug.println(" computed "
313: + toHex(computed));
314: debug.println();
315: }
316:
317: if (MessageDigest.isEqual(computed, expected)) {
318: oneDigestVerified = true;
319: ok = true;
320: } else {
321: // attempt to fallback to the workaround
322: if (!workaround) {
323: computed = mde.digestWorkaround(digest);
324: if (MessageDigest.isEqual(computed,
325: expected)) {
326: if (debug != null) {
327: debug.println(" re-computed "
328: + toHex(computed));
329: debug.println();
330: }
331: workaround = true;
332: oneDigestVerified = true;
333: ok = true;
334: }
335: }
336: }
337: if (!ok) {
338: throw new SecurityException("invalid "
339: + digest.getAlgorithm()
340: + " signature file digest for "
341: + name);
342: }
343: }
344: }
345: }
346: }
347: return oneDigestVerified;
348: }
349:
350: /**
351: * Given the PKCS7 blocks and SignerInfo[], create a Vector
352: * of certificate objects. We do this only *once* for a given
353: * signature block file.
354: */
355: private Certificate[] getCertificates(SignerInfo infos[],
356: PKCS7 block) {
357:
358: ArrayList certs = null;
359:
360: for (int i = 0; i < infos.length; i++) {
361: SignerInfo info = infos[i];
362: try {
363: ArrayList certChain = info.getCertificateChain(block);
364: if (certs == null)
365: certs = new ArrayList();
366: certs.addAll(certChain);
367: if (debug != null) {
368: debug.println("Signature Block Certificate: "
369: + (X509Certificate) certChain.get(0));
370: }
371: } catch (IOException e) {
372: }
373: }
374:
375: if (certs != null) {
376: Certificate[] certificates = new Certificate[certs.size()];
377: System.arraycopy(certs.toArray(), 0, certificates, 0, certs
378: .size());
379: return certificates;
380: } else {
381: return null;
382: }
383: }
384:
385: // for the toHex function
386: private static final char[] hexc = { '0', '1', '2', '3', '4', '5',
387: '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
388:
389: /**
390: * convert a byte array to a hex string for debugging purposes
391: * @param data the binary data to be converted to a hex string
392: * @return an ASCII hex string
393: */
394:
395: static String toHex(byte[] data) {
396:
397: StringBuffer sb = new StringBuffer(data.length * 2);
398:
399: for (int i = 0; i < data.length; i++) {
400: sb.append(hexc[(data[i] >> 4) & 0x0f]);
401: sb.append(hexc[data[i] & 0x0f]);
402: }
403: return sb.toString();
404: }
405:
406: // returns true if set contains cert
407: static boolean contains(Certificate[] set, Certificate cert) {
408: for (int i = 0; i < set.length; i++) {
409: if (set[i].equals(cert))
410: return true;
411: }
412: return false;
413: }
414:
415: // returns true if subset is a subset of set
416: static boolean isSubSet(Certificate[] subset, Certificate[] set) {
417: // check for the same object
418: if (set == subset)
419: return true;
420:
421: for (int i = 0; i < subset.length; i++) {
422: if (!contains(set, subset[i]))
423: return false;
424: }
425: return true;
426: }
427:
428: /**
429: * returns true if certs contains exactly the same certs as
430: * oldCerts and newCerts, false otherwise. oldCerts
431: * is allowed to be null.
432: */
433: static boolean matches(Certificate[] certs, Certificate[] oldCerts,
434: Certificate[] newCerts) {
435: // special case
436: if ((oldCerts == null) && (certs == newCerts))
437: return true;
438:
439: // make sure all oldCerts are in certs
440: if ((oldCerts != null) && !isSubSet(oldCerts, certs))
441: return false;
442:
443: // make sure all newCerts are in certs
444: if (!isSubSet(newCerts, certs)) {
445: return false;
446: }
447:
448: // now make sure all the certificates in certs are
449: // also in oldCerts or newCerts
450:
451: for (int i = 0; i < certs.length; i++) {
452: boolean found = ((oldCerts != null) && contains(oldCerts,
453: certs[i]))
454: || contains(newCerts, certs[i]);
455: if (!found)
456: return false;
457: }
458: return true;
459: }
460:
461: void updateCerts(Certificate[] newCerts, Hashtable certHash,
462: String name) {
463: Certificate[] oldCerts = (Certificate[]) certHash.get(name);
464:
465: // search through the cache for a match, go in reverse order
466: // as we are more likely to find a match with the last one
467: // added to the cache
468:
469: Certificate[] certs;
470: for (int i = certCache.size() - 1; i != -1; i--) {
471: certs = (Certificate[]) certCache.get(i);
472: if (matches(certs, oldCerts, newCerts)) {
473: certHash.put(name, certs);
474: return;
475: }
476: }
477:
478: if (oldCerts == null) {
479: certs = newCerts;
480: } else {
481: certs = new Certificate[oldCerts.length + newCerts.length];
482: System.arraycopy(oldCerts, 0, certs, 0, oldCerts.length);
483: System.arraycopy(newCerts, 0, certs, oldCerts.length,
484: newCerts.length);
485: }
486: certCache.add(certs);
487: certHash.put(name, certs);
488: }
489: }
|