View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  package org.xnap.plugin;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.security.KeyFactory;
26  import java.security.NoSuchAlgorithmException;
27  import java.security.Principal;
28  import java.security.PublicKey;
29  import java.security.cert.Certificate;
30  import java.security.cert.X509Certificate;
31  import java.security.interfaces.DSAPublicKey;
32  import java.security.spec.InvalidKeySpecException;
33  import java.security.spec.KeySpec;
34  import java.security.spec.X509EncodedKeySpec;
35  import java.util.ArrayList;
36  import java.util.Enumeration;
37  import java.util.List;
38  import java.util.jar.JarEntry;
39  import java.util.jar.JarFile;
40  
41  import org.apache.log4j.Logger;
42  import org.apache.xerces.utils.Base64;
43  import org.xnap.XNap;
44  import org.xnap.loader.XNapClassLoader;
45  
46  /***
47   * This class verifies the signature of the given Jar and provides
48   * informations about its signers
49   * 
50   * @version $Id: PluginVerifier.java,v 1.7 2003/11/04 13:51:06 vanto Exp $
51   */
52  public class PluginVerifier {
53  	
54  	private static Logger logger = Logger.getLogger(PluginVerifier.class);
55  	
56  	private PluginInfo info;
57  	private List certs = new ArrayList();
58  	private boolean valid;
59  	private boolean signed;
60  	private List unsignedJars = new ArrayList();
61  	private List signedJars = new ArrayList();
62  	
63  	/***
64  	 * Initialized a new PluginVerifier
65  	 * It verfies all jars used by the plugin and provides information
66  	 * about its signers. 
67  	 */
68  	public PluginVerifier(PluginInfo info) throws IOException {
69  		this.info = info;
70  		this.valid = true;
71  		this.signed = true;
72  		try {
73  			String[] classpath = info.getClassPath();
74  			for (int i = 0; i < classpath.length; i++) {
75  				if (XNapClassLoader.isLoaded(new File(classpath[i]).toURL())) break;
76  				boolean s = validateJar(classpath[i]);
77  				if (!s) {
78  					unsignedJars.add(classpath[i]);
79  				} else {
80  					signedJars.add(classpath[i]);
81  				}
82  				signed = signed && s;
83  			}
84  		} catch (SecurityException e) {
85  			this.valid = false;
86  		}
87  	}
88  	
89  	/***
90  	 * Returns true if all jars are consistent and if signed, the signature
91  	 * must be valid. */
92  	public boolean isValid() {
93  		return valid;
94   	}
95   	
96  	/***
97  	 * Return true, (only) if all jars of the plugin are signed. */
98  	public boolean isSigned() {
99  		return valid && signed;
100 	}
101 
102 	/***
103 	 * Returns true, if all jars are signed and trusted by XNap */
104 	public boolean isTrustedByXNap() {
105 		return isTrustedBy(XNap.TRUSTED_KEYS);
106 	}
107 	
108 	/***
109 	 * Returns true, if all jars are signed and trusted by the given keyset 
110 	 * @param encodedPKs StringArray of Base64-encoded X509-PublicKeys */
111 	public boolean isTrustedBy(String[] encodedPKs) {
112 		boolean trusted = false;
113 		if (isSigned()) {
114 			trusted = true;
115 			try {
116 				KeyFactory kf = KeyFactory.getInstance("DSA");
117 				for (int i = 0; i < getCertificates().length; i++) {
118 					if (getCertificates()[i] instanceof X509Certificate) {
119 						X509Certificate cert = (X509Certificate)getCertificates()[i];
120 						DSAPublicKey pk = (DSAPublicKey)cert.getPublicKey();
121 						for (int j = 0; j < encodedPKs.length; j++) {
122 							KeySpec ks = new X509EncodedKeySpec(Base64.decode(encodedPKs[j].getBytes()));
123 							PublicKey trustedKey = kf.generatePublic(ks);
124 							trusted = trusted && trustedKey.equals(pk);
125 						}
126 					}
127 				}
128 			} catch (NoSuchAlgorithmException e) {
129 				logger.debug(e);
130 			} catch (InvalidKeySpecException e) {
131 				logger.debug(e);
132 			}
133 		}
134 		return trusted;	
135 	}
136 	
137 	/***
138 	 * Returns a list of unsigned jars */
139 	public String[] getUnsignedJars() {
140 		return (String[])unsignedJars.toArray(new String[0]);
141 	}
142 	
143 	/***
144 	 * Returns a list of signed jars */
145 	public String[] getSignedJars() {
146 		return (String[])signedJars.toArray(new String[0]);
147 	}
148 
149 	/***
150 	 * Returns the list of certificates (mostly X509) */
151 	public Certificate[] getCertificates() {
152 		return (Certificate[])certs.toArray(new Certificate[0]);	
153 	}
154 	
155 	/***
156 	 * Validates the given jar and updates this' fields */
157  	private boolean validateJar(String filename) throws IOException
158 	{
159 		logger.debug(new File(filename.trim()).getAbsolutePath().toString());
160 		JarFile jar = new JarFile(new File(filename.trim()).getAbsolutePath(), true);	 		
161  		Enumeration entries = jar.entries();
162  		boolean signed = false;
163  		while (entries.hasMoreElements()) {
164 			JarEntry entry = (JarEntry) entries.nextElement();
165 			InputStream in = jar.getInputStream(entry);
166 			// read whole stream to make certificates available
167 			while (in.read() != -1) {}
168 			
169 			if (entry.getCertificates() != null) {
170 				for (int i = 0; i < entry.getCertificates().length; i++) {
171 					signed = true;
172 					if (!certs.contains(entry.getCertificates()[i])) {
173 						certs.add(entry.getCertificates()[i]);
174 					}
175 				}
176 			}
177 		}
178 		return signed; 
179  	}
180 
181 	/***
182 	 * Formats the Principal to make it more human readable */
183 	public static String readablePrincipal(Principal prin) 
184 	{
185 		// FIX: replaceAll() is only available on JDK 1.4+
186 // 		return prin.getName().replaceAll("CN=", "")
187 // 				.replaceAll("OU=", "").replaceAll("O=", "\n")
188 // 				.replaceAll("L=", "\n").replaceAll("ST=", "\n")
189 // 				.replaceAll("C=", "\n").replaceAll("DE", XNap.tr("Germany"))
190 // 				.replaceAll("US", XNap.tr("United States"));
191 		return prin.getName();
192 	}
193 	
194 	/***
195 	 * Returns a String with all signers.	 */
196 	public String getSigners() 
197 	{
198 		StringBuffer sig = new StringBuffer();
199 		for (int i = 0; i < getCertificates().length; i++) {
200 			if (getCertificates()[i] instanceof X509Certificate) {
201 				X509Certificate cert = (X509Certificate)getCertificates()[i];
202 				sig.append(cert.getIssuerDN());
203 				if (i < getCertificates().length) {
204 					sig.append(", ");
205 				}
206 			}
207 		}
208 		return sig.toString();
209 	}
210 
211 }