001: /*
002: * Copyright 2005 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.security.mscapi;
027:
028: // NOTE: this class was taken from SunJCE
029: // both files should always be kept in sync
030:
031: import java.io.*;
032: import java.util.*;
033: import java.util.jar.*;
034: import java.net.URL;
035: import java.net.JarURLConnection;
036: import java.net.MalformedURLException;
037:
038: import java.security.*;
039: import java.security.cert.*;
040: import java.security.cert.Certificate;
041:
042: /**
043: * This class verifies a JAR file and all its supporting JAR files.
044: *
045: * @author Sharon Liu
046: * @since 1.6
047: */
048: final class JarVerifierImpl {
049:
050: private static final boolean debug = false;
051:
052: // The URL for the JAR file we want to verify.
053: private URL jarURL;
054:
055: /**
056: * Creates a JarVerifier object to verify the given URL.
057: *
058: * @param jarURL the JAR file to be verified.
059: */
060: JarVerifierImpl(URL jarURL) {
061: this .jarURL = jarURL;
062: }
063:
064: /**
065: * Verify the JAR file is signed by an entity which has a certificate
066: * issued by a trusted CA.
067: *
068: * @param trustedCaCerts certificates of trusted CAs.
069: */
070: void verify(X509Certificate trustedSigner) throws JarException,
071: IOException {
072: try {
073: verifyJars(jarURL, null, trustedSigner);
074: } catch (NoSuchProviderException nspe) {
075: throw new JarException("Cannot verify " + jarURL.toString());
076: } catch (CertificateException ce) {
077: throw new JarException("Cannot verify " + jarURL.toString());
078: }
079: }
080:
081: /**
082: * Verify a JAR file and all of its supporting JAR files are signed by
083: * a signer with a certificate which
084: * can be traced back to a trusted CA.
085: */
086: private void verifyJars(URL jarURL, Vector verifiedJarsCache,
087: X509Certificate trustedSigner)
088: throws NoSuchProviderException, CertificateException,
089: IOException {
090: String jarURLString = jarURL.toString();
091:
092: // Check whether this JAR file has been verified before.
093: if ((verifiedJarsCache == null)
094: || !verifiedJarsCache.contains(jarURLString)) {
095:
096: // Verify just one jar file and find out the information
097: // about supporting JAR files.
098: String supportingJars = verifySingleJar(jarURL,
099: trustedSigner);
100:
101: // Add the url for the verified JAR into verifiedJarsCache.
102: if (verifiedJarsCache != null)
103: verifiedJarsCache.addElement(jarURLString);
104:
105: // Verify all supporting JAR files if there are any.
106: if (supportingJars != null) {
107: if (verifiedJarsCache == null) {
108: verifiedJarsCache = new Vector();
109: verifiedJarsCache.addElement(jarURLString);
110: }
111: verifyManifestClassPathJars(jarURL, supportingJars,
112: verifiedJarsCache, trustedSigner);
113: }
114: }
115:
116: }
117:
118: private void verifyManifestClassPathJars(URL baseURL,
119: String supportingJars, Vector verifiedJarsCache,
120: X509Certificate trustedSigner)
121: throws NoSuchProviderException, CertificateException,
122: IOException {
123: // Get individual JAR file names
124: String[] jarFileNames = parseAttrClasspath(supportingJars);
125:
126: try {
127: // For each JAR file, verify it
128: for (int i = 0; i < jarFileNames.length; i++) {
129: URL url = new URL(baseURL, jarFileNames[i]);
130: verifyJars(url, verifiedJarsCache, trustedSigner);
131: }
132: } catch (MalformedURLException mue) {
133: MalformedURLException ex = new MalformedURLException(
134: "The JAR file "
135: + baseURL.toString()
136: + " contains invalid URLs in its Class-Path attribute");
137: ex.initCause(mue);
138: throw ex;
139: }
140: }
141:
142: /*
143: * Verify the signature on the JAR file and return
144: * the value of the manifest attribute "CLASS_PATH".
145: * If the manifest doesn't contain the attribute
146: * "CLASS_PATH", return null.
147: */
148: private String verifySingleJar(URL jarURL,
149: X509Certificate trustedSigner)
150: throws NoSuchProviderException, CertificateException,
151: IOException {
152: // If the protocol of jarURL isn't "jar", we should
153: // construct a JAR URL so we can open a JarURLConnection
154: // for verifying this provider.
155: final URL url = jarURL.getProtocol().equalsIgnoreCase("jar") ? jarURL
156: : new URL("jar:" + jarURL.toString() + "!/");
157:
158: JarFile jf = null;
159:
160: try {
161: try {
162: jf = (JarFile) AccessController
163: .doPrivileged(new PrivilegedExceptionAction() {
164: public Object run() throws Exception {
165: JarURLConnection conn = (JarURLConnection) url
166: .openConnection();
167: return conn.getJarFile();
168: }
169: });
170: } catch (java.security.PrivilegedActionException pae) {
171: SecurityException se = new SecurityException(
172: "Cannot verify " + url.toString());
173: se.initCause(pae);
174: throw se;
175: }
176:
177: // Read in each jar entry, so the subsequent call
178: // JarEntry.getCertificates() will return Certificates
179: // for signed entries.
180: // Note: Since jars signed by 3rd party tool, e.g.,
181: // netscape signtool, may have its manifest at the
182: // end, two separate loops maybe necessary.
183: byte[] buffer = new byte[8192];
184: Vector entriesVec = new Vector();
185:
186: Enumeration entries = jf.entries();
187: while (entries.hasMoreElements()) {
188: JarEntry je = (JarEntry) entries.nextElement();
189: entriesVec.addElement(je);
190: InputStream is = jf.getInputStream(je);
191: int n;
192: try {
193: while ((n = is.read(buffer, 0, buffer.length)) != -1) {
194: // we just read. this will throw a SecurityException
195: // if a signature/digest check fails.
196: }
197: } finally {
198: is.close();
199: }
200: }
201:
202: // Throws JarException if the JAR has no manifest
203: // which means the JAR isn't signed.
204: Manifest man = jf.getManifest();
205: if (man == null)
206: throw new JarException(jarURL.toString()
207: + " is not signed.");
208:
209: // Make sure every class file in the JAR is signed properly:
210: // We must check whether the signer's certificate
211: // can be traced back to a trusted CA
212: // Once we've verified that a signer's cert can be
213: // traced back to a trusted CA, the signer's cert
214: // is kept in the cache 'verifiedSignerCache'.
215: Enumeration e = jf.entries();
216: while (e.hasMoreElements()) {
217: JarEntry je = (JarEntry) e.nextElement();
218:
219: if (je.isDirectory())
220: continue;
221:
222: // Every file must be signed except files under META-INF.
223: Certificate[] certs = je.getCertificates();
224: if ((certs == null) || (certs.length == 0)) {
225: if (!je.getName().startsWith("META-INF"))
226: throw new JarException(jarURL.toString()
227: + " has unsigned entries - "
228: + je.getName());
229: } else {
230: // A JAR file may be signed by multiple signers.
231: // So certs may contain mutiple certificate
232: // chains. Check whether at least one of the signers
233: // can be trusted.
234: // The signer is trusted iff
235: // 1) its certificate chain can be verified
236: // 2) the last certificate on its chain is one of
237: // JCE's trusted CAs
238: // OR
239: // the last certificate on its chain is issued
240: // by one of JCE's trusted CAs.
241:
242: int startIndex = 0;
243: X509Certificate[] certChain;
244: boolean signedAsExpected = false;
245:
246: while ((certChain = getAChain(certs, startIndex)) != null) {
247: // Verify that the signer matches trustedSigner
248: if (trustedSigner.equals(certChain[0])) {
249: signedAsExpected = true;
250: break;
251: }
252:
253: // Proceed to the next chain.
254: startIndex += certChain.length;
255: }
256:
257: if (!signedAsExpected) {
258: throw new JarException(jarURL.toString()
259: + " is not signed by a"
260: + " trusted signer.");
261: }
262: }
263: }
264:
265: // Return the value of the attribute CLASS_PATH
266: return man.getMainAttributes().getValue(
267: Attributes.Name.CLASS_PATH);
268: } finally {
269: if (jf != null) {
270: jf = null;
271: }
272: }
273: }
274:
275: /*
276: * Parse the manifest attribute Class-Path.
277: */
278: private static String[] parseAttrClasspath(String supportingJars)
279: throws JarException {
280: supportingJars = supportingJars.trim();
281:
282: int endIndex = supportingJars.indexOf(' ');
283: String name = null;
284: Vector values = new Vector();
285: boolean done = false;
286:
287: do {
288: if (endIndex > 0) {
289: name = supportingJars.substring(0, endIndex);
290: // Since supportingJars has been trimmed,
291: // endIndex + 1 must be less than
292: // supportingJars.length().
293: supportingJars = supportingJars.substring(endIndex + 1)
294: .trim();
295: endIndex = supportingJars.indexOf(' ');
296: } else {
297: name = supportingJars;
298: done = true;
299: }
300: if (name.endsWith(".jar")) {
301: values.addElement(name);
302: } else {
303: // Cannot verify. Throw a JarException.
304: throw new JarException("The provider contains "
305: + "un-verifiable components");
306: }
307: } while (!done);
308:
309: String[] result = new String[values.size()];
310: values.copyInto(result);
311:
312: return result;
313: }
314:
315: private static X509Certificate[] getAChain(Certificate[] certs,
316: int startIndex) {
317: int i;
318:
319: if (startIndex > certs.length - 1)
320: return null;
321:
322: for (i = startIndex; i < certs.length - 1; i++) {
323: if (!((X509Certificate) certs[i + 1]).getSubjectDN()
324: .equals(((X509Certificate) certs[i]).getIssuerDN())) {
325: break;
326: }
327: }
328: int certChainSize = (i - startIndex) + 1;
329: X509Certificate[] ret = new X509Certificate[certChainSize];
330: for (int j = 0; j < certChainSize; j++) {
331: ret[j] = (X509Certificate) certs[startIndex + j];
332: }
333: return ret;
334: }
335:
336: static boolean doVerification(final Class cc,
337: final String PROVIDERCERT) {
338: X509Certificate providerCert;
339: try {
340: CertificateFactory certificateFactory = CertificateFactory
341: .getInstance("X.509");
342: byte[] b = PROVIDERCERT.getBytes("UTF8");
343: providerCert = (X509Certificate) certificateFactory
344: .generateCertificate(new ByteArrayInputStream(b));
345: } catch (Exception e) {
346: if (debug) {
347: e.printStackTrace();
348: }
349: return false;
350: }
351:
352: URL url = (URL) AccessController
353: .doPrivileged(new PrivilegedAction() {
354: public Object run() {
355: CodeSource s1 = cc.getProtectionDomain()
356: .getCodeSource();
357: return s1.getLocation();
358: }
359: });
360: if (url == null) {
361: return false;
362: }
363:
364: JarVerifierImpl jv = new JarVerifierImpl(url);
365: try {
366: jv.verify(providerCert);
367: } catch (Exception e) {
368: return false;
369: }
370:
371: return true;
372: }
373:
374: }
|