001: /*
002: * @(#)ManifestEntryVerifier.java 1.21 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.*;
031: import java.io.*;
032: import java.util.*;
033: import java.util.jar.*;
034:
035: import sun.misc.BASE64Decoder;
036:
037: /**
038: * This class is used to verify each entry in a jar file with its
039: * manifest value.
040: */
041:
042: public class ManifestEntryVerifier {
043:
044: private static final Debug debug = Debug.getInstance("jar");
045:
046: private static final Provider sunProvider = new sun.security.provider.Sun();
047:
048: /** the created digest objects */
049: HashMap createdDigests;
050:
051: /** the digests in use for a given entry*/
052: ArrayList digests;
053:
054: /** the manifest hashes for the digests in use */
055: ArrayList manifestHashes;
056:
057: /** the hash values in the Manifest */
058: private byte manifestHash[][] = { null, null };
059: private BASE64Decoder decoder = null;
060: private String name = null;
061: private Manifest man;
062:
063: private boolean skip = true;
064:
065: private JarEntry entry;
066:
067: private java.security.cert.Certificate[] certs = null;
068:
069: /**
070: * Create a new ManifestEntryVerifier object.
071: */
072: public ManifestEntryVerifier(Manifest man) {
073: createdDigests = new HashMap(11);
074: digests = new ArrayList();
075: manifestHashes = new ArrayList();
076: decoder = new BASE64Decoder();
077: this .man = man;
078: }
079:
080: /**
081: * Find the hashes in the
082: * manifest for this entry, save them, and set the MessageDigest
083: * objects to calculate the hashes on the fly. If name is
084: * null it signifies that update/verify should ignore this entry.
085: */
086: public void setEntry(String name, JarEntry entry)
087: throws IOException {
088: digests.clear();
089: manifestHashes.clear();
090: this .name = name;
091: this .entry = entry;
092:
093: skip = true;
094: certs = null;
095:
096: if (man == null || name == null) {
097: return;
098: }
099:
100: /* get the headers from the manifest for this entry */
101: /* if there aren't any, we can't verify any digests for this entry */
102:
103: Attributes attr = man.getAttributes(name);
104: if (attr == null) {
105: // We should be able to remove this at some point.
106: // there are broken jars floating around with ./name and /name
107: // in the manifest, and "name" in the zip/jar file.
108: attr = man.getAttributes("./" + name);
109: if (attr == null) {
110: attr = man.getAttributes("/" + name);
111: if (attr == null)
112: return;
113: }
114: }
115:
116: Iterator it = attr.entrySet().iterator();
117:
118: while (it.hasNext()) {
119: Map.Entry se = (Map.Entry) it.next();
120: String key = se.getKey().toString();
121:
122: if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
123: // 7 is length of "-Digest"
124: String algorithm = key.substring(0, key.length() - 7);
125:
126: MessageDigest digest = (MessageDigest) createdDigests
127: .get(algorithm);
128:
129: if (digest == null) {
130: try {
131:
132: digest = MessageDigest.getInstance(algorithm,
133: sunProvider);
134: createdDigests.put(algorithm, digest);
135: } catch (NoSuchAlgorithmException nsae) {
136: // ignore
137: }
138: }
139:
140: if (digest != null) {
141: skip = false;
142: digest.reset();
143: digests.add(digest);
144: manifestHashes.add(decoder.decodeBuffer((String) se
145: .getValue()));
146: }
147: }
148: }
149: }
150:
151: /**
152: * update the digests for the digests we are interested in
153: */
154: public void update(byte buffer) {
155: if (skip)
156: return;
157:
158: for (int i = 0; i < digests.size(); i++) {
159: ((MessageDigest) digests.get(i)).update(buffer);
160: }
161: }
162:
163: /**
164: * update the digests for the digests we are interested in
165: */
166: public void update(byte buffer[], int off, int len) {
167: if (skip)
168: return;
169:
170: for (int i = 0; i < digests.size(); i++) {
171: ((MessageDigest) digests.get(i)).update(buffer, off, len);
172: }
173: }
174:
175: /**
176: * get the JarEntry for this object
177: */
178: public JarEntry getEntry() {
179: return entry;
180: }
181:
182: /**
183: * go through all the digests, calculating the final digest
184: * and comparing it to the one in the manifest. If this is
185: * the first time we have verified this object, remove its
186: * Certs from sigFileCerts and place in verifiedCerts.
187: *
188: *
189: */
190: public java.security.cert.Certificate[] verify(
191: Hashtable verifiedCerts, Hashtable sigFileCerts)
192: throws JarException {
193: if (skip)
194: return null;
195:
196: if (certs != null)
197: return certs;
198:
199: for (int i = 0; i < digests.size(); i++) {
200:
201: MessageDigest digest = (MessageDigest) digests.get(i);
202: byte[] manHash = (byte[]) manifestHashes.get(i);
203: byte[] theHash = digest.digest();
204:
205: if (debug != null) {
206: debug.println("Manifest Entry: " + name + " digest="
207: + digest.getAlgorithm());
208: debug.println(" manifest " + toHex(manHash));
209: debug.println(" computed " + toHex(theHash));
210: debug.println();
211: }
212:
213: if (!MessageDigest.isEqual(theHash, manHash))
214: throw new SecurityException(digest.getAlgorithm()
215: + " digest error for " + name);
216: }
217:
218: // take it out of sigFileCerts and put it in verifiedIds...
219: certs = (java.security.cert.Certificate[]) sigFileCerts
220: .remove(name);
221: if (certs != null) {
222: verifiedCerts.put(name, certs);
223: }
224: return certs;
225: }
226:
227: // for the toHex function
228: private static final char[] hexc = { '0', '1', '2', '3', '4', '5',
229: '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
230:
231: /**
232: * convert a byte array to a hex string for debugging purposes
233: * @param data the binary data to be converted to a hex string
234: * @return an ASCII hex string
235: */
236:
237: static String toHex(byte[] data) {
238:
239: StringBuffer sb = new StringBuffer(data.length * 2);
240:
241: for (int i = 0; i < data.length; i++) {
242: sb.append(hexc[(data[i] >> 4) & 0x0f]);
243: sb.append(hexc[data[i] & 0x0f]);
244: }
245: return sb.toString();
246: }
247:
248: }
|