KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > crypto > JarVerifier


1 /*
2  * @(#)JarVerifier.java 1.17 04/01/06
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.crypto;
9
10 import java.io.*;
11 import java.security.AccessController;
12 import java.security.PublicKey;
13 import java.security.NoSuchProviderException;
14 import java.security.PrivilegedExceptionAction;
15 import java.security.cert.*;
16 import java.util.*;
17 import java.net.URL;
18 import java.net.JarURLConnection;
19 import java.net.MalformedURLException;
20 import java.util.jar.*;
21 import java.util.zip.ZipEntry;
22
23 import sun.security.validator.Validator;
24
25 /**
26  * This class verifies a JAR file and all its supporting JAR files.
27  *
28  * @version 1.17, 01/06/04
29  * @author Sharon Liu
30  * @since 1.4
31  */

32 final class JarVerifier {
33
34     private static final boolean debug = false;
35
36     // Cache for holding provider certificates which has been verified
37
private Vector verifiedSignerCache = null;
38
39     // The URL for the JAR file we want to verify.
40
private URL jarURL;
41
42     // The JarFile for the JAR file we verified.
43
private JarFile jarFile = null;
44     
45     private Validator validator;
46
47     /**
48      * Creates a JarVerifier object to verify the given URL.
49      *
50      * @param jarURL the JAR file to be verified.
51      */

52     JarVerifier(URL jarURL, Validator validator) {
53     this.jarURL = jarURL;
54     this.validator = validator;
55     verifiedSignerCache = new Vector(2);
56     }
57
58     /**
59      * Verify the JAR file is signed by an entity which has a certificate
60      * issued by a trusted CA.
61      *
62      * @param trustedCaCerts certificates of trusted CAs.
63      */

64     void verify() throws JarException, IOException {
65     if (jarURL == null) {
66         throw new JarException("Class is on the bootclasspath");
67     }
68     try {
69         verifyJars(jarURL, null);
70     } catch (NoSuchProviderException nspe) {
71         throw new JarException("Cannot verify " + jarURL.toString());
72     } catch (CertificateException ce) {
73         throw new JarException("Cannot verify " + jarURL.toString());
74     } finally {
75         verifiedSignerCache = null;
76     }
77     }
78
79     /**
80      * Returns a JarFile for the <code>jarURL</code> passed to the
81      * constructor of this class.
82      *
83      * One should call verifyJar() before calling getJarFile(); otherwise,
84      * null is returned.
85      *
86      * The caller of getJarFile() should close the returned JarFile.
87      */

88     JarFile getJarFile() {
89     return jarFile;
90     }
91
92     /**
93      * Verify a JAR file and all of its supporting JAR files are signed by
94      * a signer with a certificate which
95      * can be traced back to a trusted CA.
96      */

97     private void verifyJars(URL jarURL, Vector verifiedJarsCache)
98         throws NoSuchProviderException, CertificateException, IOException
99     {
100     String jarURLString = jarURL.toString();
101
102     // Check whether this JAR file has been verified before.
103
if ((verifiedJarsCache == null) ||
104         !verifiedJarsCache.contains(jarURLString)) {
105         
106         // Verify just one jar file and find out the information
107
// about supporting JAR files.
108
String supportingJars = verifySingleJar(jarURL);
109
110         // Add the url for the verified JAR into verifiedJarsCache.
111
if (verifiedJarsCache != null)
112         verifiedJarsCache.addElement(jarURLString);
113
114         // Verify all supporting JAR files if there are any.
115
if (supportingJars != null) {
116         if (verifiedJarsCache == null) {
117             verifiedJarsCache = new Vector();
118             verifiedJarsCache.addElement(jarURLString);
119         }
120         verifyManifestClassPathJars(jarURL,
121                         supportingJars,
122                         verifiedJarsCache);
123         }
124     }
125     
126     }
127
128     private void verifyManifestClassPathJars(URL baseURL,
129                        String supportingJars,
130                        Vector verifiedJarsCache)
131         throws NoSuchProviderException, CertificateException, IOException
132     {
133     // Get individual JAR file names
134
String[] jarFileNames = parseAttrClasspath(supportingJars);
135
136     try {
137         // For each JAR file, verify it
138
for (int i = 0; i < jarFileNames.length; i++) {
139         URL url = new URL(baseURL, jarFileNames[i]);
140         verifyJars(url, verifiedJarsCache);
141         }
142     } catch (MalformedURLException mue) {
143         MalformedURLException ex = new MalformedURLException(
144         "The JAR file " + baseURL.toString() +
145         " contains invalid URLs in its Class-Path attribute");
146         ex.initCause(mue);
147         throw ex;
148     }
149     }
150
151     /*
152      * Verify the signature on the JAR file and return
153      * the value of the manifest attribute "CLASS_PATH".
154      * If the manifest doesn't contain the attribute
155      * "CLASS_PATH", return null.
156      */

157     private String verifySingleJar(URL jarURL)
158         throws NoSuchProviderException, CertificateException, IOException
159     {
160     // If the protocol of jarURL isn't "jar", we should
161
// construct a JAR URL so we can open a JarURLConnection
162
// for verifying this provider.
163
final URL url = jarURL.getProtocol().equalsIgnoreCase("jar")?
164                     jarURL : new URL("jar:" + jarURL.toString() + "!/");
165
166     JarFile jf = null;
167     boolean isCached = true;
168
169     try {
170         try {
171         jf = (JarFile) AccessController.doPrivileged(
172                    new PrivilegedExceptionAction() {
173             public Object run() throws Exception {
174             JarURLConnection conn = (JarURLConnection)
175                 url.openConnection();
176             return conn.getJarFile();
177             }
178         });
179         } catch (java.security.PrivilegedActionException pae) {
180         SecurityException se = new SecurityException(
181             "Cannot verify " + url.toString());
182         se.initCause(pae);
183         throw se;
184         }
185
186         // Read in each jar entry, so the subsequent call
187
// JarEntry.getCertificates() will return Certificates
188
// for signed entries.
189
// Note: Since jars signed by 3rd party tool, e.g.,
190
// netscape signtool, may have its manifest at the
191
// end, two separate loops maybe necessary.
192
byte[] buffer = new byte[8192];
193         Vector entriesVec = new Vector();
194         
195         Enumeration entries = jf.entries();
196         while (entries.hasMoreElements()) {
197         JarEntry je = (JarEntry)entries.nextElement();
198         entriesVec.addElement(je);
199         BufferedInputStream is =
200             new BufferedInputStream(jf.getInputStream(je));
201         int n;
202         try {
203             while ((n = is.read(buffer, 0, buffer.length)) != -1) {
204                 // we just read. this will throw a SecurityException
205
// if a signature/digest check fails.
206
}
207         } finally {
208             is.close();
209         }
210         }
211     
212         if (this.jarURL.equals(jarURL)) {
213         this.jarFile = jf;
214         } else {
215         // have to close the jar file at the end of this routine
216
isCached = false;
217         }
218
219         // Throws JarException if the JAR has no manifest
220
// which means the JAR isn't signed.
221
Manifest man = jf.getManifest();
222         if (man == null)
223         throw new JarException(jarURL.toString() + " is not signed.");
224         
225         // Make sure every class file in the JAR is signed properly:
226
// We must check whether the signer's certificate
227
// can be traced back to a trusted CA
228
// Once we've verified that a signer's cert can be
229
// traced back to a trusted CA, the signer's cert
230
// is kept in the cache 'verifiedSignerCache'.
231
Enumeration e = jf.entries();
232         while (e.hasMoreElements()) {
233         JarEntry je = (JarEntry) e.nextElement();
234         
235         if (je.isDirectory())
236             continue;
237         
238         // Every file must be signed except files under META-INF.
239
Certificate[] certs = je.getCertificates();
240         if ((certs == null) || (certs.length == 0)) {
241             if (!je.getName().startsWith("META-INF"))
242             throw new JarException(jarURL.toString() +
243                            " has unsigned entries - "
244                            + je.getName() );
245         } else {
246             // A JAR file may be signed by multiple signers.
247
// So certs may contain mutiple certificate
248
// chains. Check whether at least one of the signers
249
// can be trusted.
250
// The signer is trusted iff
251
// 1) its certificate chain can be verified
252
// 2) the last certificate on its chain is one of
253
// JCE's trusted CAs
254
// OR
255
// the last certificate on its chain is issued
256
// by one of JCE's trusted CAs.
257

258             int startIndex = 0;
259             X509Certificate[] certChain;
260             boolean signedAsExpected = false;
261             
262             while ((certChain = getAChain(certs, startIndex)) != null) {
263             // First, check if the signer has been successfully
264
// verified before.
265
// If not, verify the signature for all certificates
266
// in the chain with the specified Trusted CA
267
// certificates.
268
if (verifiedSignerCache.contains(
269                 (X509Certificate)certChain[0])) {
270                 signedAsExpected = true;
271                 break;
272             } else if (isTrusted(certChain)) {
273                 signedAsExpected = true;
274                 verifiedSignerCache.addElement(certChain[0]);
275                 break;
276             }
277             // Proceed to the next chain.
278
startIndex += certChain.length;
279             }
280
281             if (!signedAsExpected) {
282             throw new JarException(jarURL.toString() +
283                            " is not signed by a" +
284                            " trusted signer.");
285             }
286         }
287         }
288         
289         // Return the value of the attribute CLASS_PATH
290
return man.getMainAttributes().getValue(
291                Attributes.Name.CLASS_PATH);
292     } finally {
293         if ((jf != null) && (isCached == false)) {
294         jf = null;
295         }
296     }
297     }
298
299     /*
300      * Parse the manifest attribute Class-Path.
301      */

302     private static String[] parseAttrClasspath(String supportingJars)
303         throws JarException
304     {
305     supportingJars = supportingJars.trim();
306
307     int endIndex = supportingJars.indexOf(' ');
308     String name = null;
309     Vector values = new Vector();
310     boolean done = false;
311
312     do {
313         if (endIndex > 0) {
314         name = supportingJars.substring(0, endIndex);
315         // Since supportingJars has been trimmed,
316
// endIndex + 1 must be less than
317
// supportingJars.length().
318
supportingJars = supportingJars.substring(endIndex + 1).trim();
319         endIndex = supportingJars.indexOf(' ');
320         } else {
321         name = supportingJars;
322         done = true;
323         }
324         if (name.endsWith(".jar")) {
325         values.addElement(name);
326         } else {
327         // Cannot verify. Throw a JarException.
328
throw new JarException("The provider contains " +
329                        "un-verifiable components");
330         }
331     } while (!done);
332     
333     return (String[]) values.toArray(new String[0]);
334     }
335
336     private boolean isTrusted(X509Certificate chain[]) {
337     try {
338         validator.validate(chain);
339         return true;
340     } catch (CertificateException e) {
341         return false;
342     }
343     }
344
345     // Retrieve a certificate chain from the specified certificate
346
// array which may hold multiple cert chains starting from index
347
// 'startIndex'.
348
private static X509Certificate[] getAChain(Certificate[] certs,
349                     int startIndex) {
350     int i;
351
352     if (startIndex > certs.length - 1)
353         return null;
354
355     for (i = startIndex; i < certs.length - 1; i++) {
356         if (!((X509Certificate)certs[i + 1]).getSubjectDN().equals(
357           ((X509Certificate)certs[i]).getIssuerDN())) {
358         break;
359         }
360     }
361     int certChainSize = (i-startIndex) + 1;
362     X509Certificate[] ret = new X509Certificate[certChainSize];
363     for (int j = 0; j < certChainSize; j++ ) {
364         ret[j] = (X509Certificate) certs[startIndex + j];
365     }
366     return ret;
367     }
368
369     // Convert the specified certificate array into an array of
370
// certificate chains.
371
// NOTE: returns an empty List if certs is null
372
static List convertCertsToChains(Certificate[] certs)
373         throws CertificateException {
374     if (certs == null) {
375         return Collections.EMPTY_LIST;
376     }
377     List list = new ArrayList();
378     X509Certificate[] oneChain = null;
379     int index = 0;
380     while ((oneChain = getAChain(certs, index)) != null) {
381         list.add(oneChain);
382         // Proceed to the next chain.
383
index += oneChain.length;
384     }
385     
386     return list;
387     }
388
389     // Retrieve the signers information for the specified jar entry.
390
// NOTE: returns an empty List if no certificate chain is found
391
static List getSignersOfJarEntry(URL jarEntryURL) throws Exception {
392     JarURLConnection conn = (JarURLConnection) jarEntryURL.openConnection();
393     byte[] buffer = new byte[8192];
394     // Read in the JarEntry data to retrieve the cert chains
395
BufferedInputStream is = new BufferedInputStream(conn.getInputStream());
396     try {
397         while (is.read(buffer, 0, buffer.length) != -1) {
398             // Read - this will throw a SecurityException
399
// if a signature/digest check fails.
400
}
401     } finally {
402         is.close();
403     }
404     
405     return convertCertsToChains(conn.getCertificates());
406     }
407
408     protected void finalize() throws Throwable {
409     jarFile = null;
410     super.finalize();
411     }
412 }
413
Popular Tags