001 /* CodeSource.java -- Code location and certifcates 002 Copyright (C) 1998, 2002, 2004 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath 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 for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package java.security; 040 041 import java.io.ByteArrayInputStream; 042 import java.io.IOException; 043 import java.io.ObjectInputStream; 044 import java.io.ObjectOutputStream; 045 import java.io.Serializable; 046 import java.net.SocketPermission; 047 import java.net.URL; 048 // Note that this overrides Certificate in this package. 049 import java.security.cert.Certificate; 050 import java.security.cert.CertificateEncodingException; 051 import java.security.cert.CertificateException; 052 import java.security.cert.CertificateFactory; 053 import java.util.Arrays; 054 import java.util.HashSet; 055 import java.util.Iterator; 056 057 /** 058 * This class represents a location from which code is loaded (as 059 * represented by a URL), and the list of certificates that are used to 060 * check the signatures of signed code loaded from this source. 061 * 062 * @author Aaron M. Renn (arenn@urbanophile.com) 063 * @author Eric Blake (ebb9@email.byu.edu) 064 * @since 1.1 065 * @status updated to 1.4 066 */ 067 public class CodeSource implements Serializable 068 { 069 /** 070 * Compatible with JDK 1.1+. 071 */ 072 private static final long serialVersionUID = 4977541819976013951L; 073 074 /** 075 * This is the URL that represents the code base from which code will 076 * be loaded. 077 * 078 * @serial the code location 079 */ 080 private final URL location; 081 082 /** The set of certificates for this code base. */ 083 private transient HashSet certs; 084 085 /** 086 * This creates a new instance of <code>CodeSource</code> that loads code 087 * from the specified URL location and which uses the specified certificates 088 * for verifying signatures. 089 * 090 * @param location the location from which code will be loaded 091 * @param certs the list of certificates 092 */ 093 public CodeSource(URL location, Certificate[] certs) 094 { 095 this.location = location; 096 if (certs != null) 097 this.certs = new HashSet(Arrays.asList(certs)); 098 } 099 100 /** 101 * This method returns a hash value for this object. 102 * 103 * @return a hash value for this object 104 */ 105 public int hashCode() 106 { 107 return (location == null ? 0 : location.hashCode()) 108 ^ (certs == null ? 0 : certs.hashCode()); 109 } 110 111 /** 112 * This method tests the specified <code>Object</code> for equality with 113 * this object. This will be true if and only if the locations are equal 114 * and the certificate sets are identical (ignoring order). 115 * 116 * @param obj the <code>Object</code> to test against 117 * @return true if the specified object is equal to this one 118 */ 119 public boolean equals(Object obj) 120 { 121 if (! (obj instanceof CodeSource)) 122 return false; 123 CodeSource cs = (CodeSource) obj; 124 return (certs == null ? cs.certs == null : certs.equals(cs.certs)) 125 && (location == null ? cs.location == null 126 : location.equals(cs.location)); 127 } 128 129 /** 130 * This method returns the URL specifying the location from which code 131 * will be loaded under this <code>CodeSource</code>. 132 * 133 * @return the code location for this <code>CodeSource</code> 134 */ 135 public final URL getLocation() 136 { 137 return location; 138 } 139 140 /** 141 * This method returns the list of digital certificates that can be used 142 * to verify the signatures of code loaded under this 143 * <code>CodeSource</code>. 144 * 145 * @return the certifcate list for this <code>CodeSource</code> 146 */ 147 public final Certificate[] getCertificates() 148 { 149 if (certs == null) 150 return null; 151 Certificate[] c = new Certificate[certs.size()]; 152 certs.toArray(c); 153 return c; 154 } 155 156 /** 157 * This method tests to see if a specified <code>CodeSource</code> is 158 * implied by this object. Effectively, to meet this test, the specified 159 * object must have all the certifcates this object has (but may have more), 160 * and must have a location that is a subset of this object's. In order 161 * for this object to imply the specified object, the following must be 162 * true: 163 * 164 * <ol> 165 * <li><em>codesource</em> must not be <code>null</code>.</li> 166 * <li>If <em>codesource</em> has a certificate list, all of it's 167 * certificates must be present in the certificate list of this 168 * code source.</li> 169 * <li>If this object does not have a <code>null</code> location, then 170 * the following addtional tests must be passed. 171 * 172 * <ol> 173 * <li><em>codesource</em> must not have a <code>null</code> 174 * location.</li> 175 * <li><em>codesource</em>'s location must be equal to this object's 176 * location, or 177 * <ul> 178 * <li><em>codesource</em>'s location protocol, port, and ref (aka, 179 * anchor) must equal this objects</li> 180 * <li><em>codesource</em>'s location host must imply this object's 181 * location host, as determined by contructing 182 * <code>SocketPermission</code> objects from each with no 183 * action list and using that classes's <code>implies</code> 184 * method</li> 185 * <li>If this object's location file ends with a '/', then the 186 * specified object's location file must start with this 187 * object's location file. Otherwise, the specified object's 188 * location file must start with this object's location file 189 * with the '/' character appended to it.</li> 190 * </ul></li> 191 * </ol></li> 192 * </ol> 193 * 194 * <p>For example, each of these locations imply the location 195 * "http://java.sun.com/classes/foo.jar":</p> 196 * 197 * <pre> 198 * http: 199 * http://*.sun.com/classes/* 200 * http://java.sun.com/classes/- 201 * http://java.sun.com/classes/foo.jar 202 * </pre> 203 * 204 * <p>Note that the code source with null location and null certificates implies 205 * all other code sources.</p> 206 * 207 * @param cs the <code>CodeSource</code> to test against this object 208 * @return true if this specified <code>CodeSource</code> is implied 209 */ 210 public boolean implies(CodeSource cs) 211 { 212 if (cs == null) 213 return false; 214 // First check the certificate list. 215 if (certs != null && (cs.certs == null || ! certs.containsAll(cs.certs))) 216 return false; 217 // Next check the location. 218 if (location == null) 219 return true; 220 if (cs.location == null 221 || ! location.getProtocol().equals(cs.location.getProtocol()) 222 || (location.getPort() != -1 223 && location.getPort() != cs.location.getPort()) 224 || (location.getRef() != null 225 && ! location.getRef().equals(cs.location.getRef()))) 226 return false; 227 if (location.getHost() != null) 228 { 229 String their_host = cs.location.getHost(); 230 if (their_host == null) 231 return false; 232 SocketPermission our_sockperm = 233 new SocketPermission(location.getHost(), "accept"); 234 SocketPermission their_sockperm = 235 new SocketPermission(their_host, "accept"); 236 if (! our_sockperm.implies(their_sockperm)) 237 return false; 238 } 239 String our_file = location.getFile(); 240 if (our_file != null) 241 { 242 if (! our_file.endsWith("/")) 243 our_file += "/"; 244 String their_file = cs.location.getFile(); 245 if (their_file == null 246 || ! their_file.startsWith(our_file)) 247 return false; 248 } 249 return true; 250 } 251 252 /** 253 * This method returns a <code>String</code> that represents this object. 254 * The result is in the format <code>"(" + getLocation()</code> followed 255 * by a space separated list of certificates (or "<no certificates>"), 256 * followed by <code>")"</code>. 257 * 258 * @return a <code>String</code> for this object 259 */ 260 public String toString() 261 { 262 StringBuffer sb = new StringBuffer("(").append(location); 263 if (certs == null || certs.isEmpty()) 264 sb.append(" <no certificates>"); 265 else 266 { 267 Iterator iter = certs.iterator(); 268 for (int i = certs.size(); --i >= 0; ) 269 sb.append(' ').append(iter.next()); 270 } 271 return sb.append(")").toString(); 272 } 273 274 /** 275 * Reads this object from a serialization stream. 276 * 277 * @param s the input stream 278 * @throws IOException if reading fails 279 * @throws ClassNotFoundException if deserialization fails 280 * @serialData this reads the location, then expects an int indicating the 281 * number of certificates. Each certificate is a String type 282 * followed by an int encoding length, then a byte[] encoding 283 */ 284 private void readObject(ObjectInputStream s) 285 throws IOException, ClassNotFoundException 286 { 287 s.defaultReadObject(); 288 int count = s.readInt(); 289 certs = new HashSet(); 290 while (--count >= 0) 291 { 292 String type = (String) s.readObject(); 293 int bytes = s.readInt(); 294 byte[] encoded = new byte[bytes]; 295 for (int i = 0; i < bytes; i++) 296 encoded[i] = s.readByte(); 297 ByteArrayInputStream stream = new ByteArrayInputStream(encoded); 298 try 299 { 300 CertificateFactory factory = CertificateFactory.getInstance(type); 301 certs.add(factory.generateCertificate(stream)); 302 } 303 catch (CertificateException e) 304 { 305 // XXX Should we ignore this certificate? 306 } 307 } 308 } 309 310 /** 311 * Writes this object to a serialization stream. 312 * 313 * @param s the output stream 314 * @throws IOException if writing fails 315 * @serialData this writes the location, then writes an int indicating the 316 * number of certificates. Each certificate is a String type 317 * followed by an int encoding length, then a byte[] encoding 318 */ 319 private void writeObject(ObjectOutputStream s) throws IOException 320 { 321 s.defaultWriteObject(); 322 if (certs == null) 323 s.writeInt(0); 324 else 325 { 326 int count = certs.size(); 327 s.writeInt(count); 328 Iterator iter = certs.iterator(); 329 while (--count >= 0) 330 { 331 Certificate c = (Certificate) iter.next(); 332 s.writeObject(c.getType()); 333 byte[] encoded; 334 try 335 { 336 encoded = c.getEncoded(); 337 } 338 catch (CertificateEncodingException e) 339 { 340 // XXX Should we ignore this certificate? 341 encoded = null; 342 } 343 if (encoded == null) 344 s.writeInt(0); 345 else 346 { 347 s.writeInt(encoded.length); 348 for (int i = 0; i < encoded.length; i++) 349 s.writeByte(encoded[i]); 350 } 351 } 352 } 353 } 354 } // class CodeSource