001: /*
002: * @(#)CodeSource.java 1.37 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 java.security;
029:
030: import java.net.URL;
031: import java.net.SocketPermission;
032: import java.util.Hashtable;
033: import java.io.ByteArrayInputStream;
034: import java.io.IOException;
035: import java.security.cert.*;
036:
037: /**
038: *
039: * <p>This class extends the concept of a codebase to
040: * encapsulate not only the location (URL) but also the certificate(s)
041: * that were used to verify signed code originating from that
042: * location.
043: *
044: * @version 1.28, 02/02/00
045: * @author Li Gong
046: * @author Roland Schemers
047: */
048:
049: public class CodeSource implements java.io.Serializable {
050:
051: /**
052: * The code location.
053: *
054: * @serial
055: */
056: private URL location;
057:
058: // certificates
059: private transient java.security.cert.Certificate certs[];
060:
061: // cached SocketPermission used for matchLocation
062: private transient SocketPermission sp;
063:
064: /**
065: * Constructs a CodeSource and associates it with the specified
066: * location and set of certificates.
067: *
068: * @param url the location (URL).
069: *
070: * @param certs the certificate(s).
071: */
072: public CodeSource(URL url, java.security.cert.Certificate certs[]) {
073: this .location = url;
074: if (certs != null)
075: this .certs = (java.security.cert.Certificate[]) certs
076: .clone();
077: }
078:
079: /**
080: * Returns the hash code value for this object.
081: *
082: * @return a hash code value for this object.
083: */
084:
085: public int hashCode() {
086: if (location != null)
087: return location.hashCode();
088: else
089: return 0;
090: }
091:
092: /**
093: * Tests for equality between the specified object and this
094: * object. Two CodeSource objects are considered equal if their
095: * locations are of identical value and if the two sets of
096: * certificates are of identical values. It is not required that
097: * the certificates be in the same order.
098: *
099: * @param obj the object to test for equality with this object.
100: *
101: * @return true if the objects are considered equal, false otherwise.
102: */
103: public boolean equals(Object obj) {
104: if (obj == this )
105: return true;
106:
107: // objects types must be equal
108: if (!(obj instanceof CodeSource))
109: return false;
110:
111: CodeSource cs = (CodeSource) obj;
112:
113: // URLs must match
114: if (location == null) {
115: // if location is null, then cs.location must be null as well
116: if (cs.location != null)
117: return false;
118: } else {
119: // if location is not null, then it must equal cs.location
120: if (!location.equals(cs.location))
121: return false;
122: }
123:
124: // certs must match
125: if (certs == null) {
126: // if certs is null, then cs.certs must be null as well
127: if (cs.certs != null)
128: return false;
129: } else {
130: // if certs is not null, then it must equal cs.certs
131: // equality means that both arrays of certs are the same set
132: // step 1 -- every cert in certs[] must match one in cs.certs[]
133: if (cs.certs == null)
134: return false;
135:
136: boolean match;
137: for (int i = 0; i < certs.length; i++) {
138: match = false;
139: for (int j = 0; j < cs.certs.length; j++) {
140: if (certs[i].equals(cs.certs[j])) {
141: match = true;
142: break;
143: }
144: }
145: if (!match)
146: return false;
147: }
148: // step 2 -- every key in cs.certs[] must match one in certs[]
149: for (int i = 0; i < cs.certs.length; i++) {
150: match = false;
151: for (int j = 0; j < certs.length; j++) {
152: if (cs.certs[i].equals(certs[j])) {
153: match = true;
154: break;
155: }
156: }
157: if (!match)
158: return false;
159: }
160: }
161:
162: // they must be equal if we got here...
163: return true;
164: }
165:
166: /**
167: * Returns the location associated with this CodeSource.
168: *
169: * @return the location (URL).
170: */
171: public final URL getLocation() {
172: /* since URL is practically immutable, returning itself is not
173: a security problem */
174: return this .location;
175: }
176:
177: /**
178: * Returns the certificates associated with this CodeSource.
179: *
180: * @return the certificates
181: */
182: public final java.security.cert.Certificate[] getCertificates() {
183: /* return a clone copy, to avoid malicious modification to the
184: original object */
185: if (this .certs != null) {
186: return (java.security.cert.Certificate[]) this .certs
187: .clone();
188: } else {
189: return null;
190: }
191: }
192:
193: /**
194: * Returns true if this CodeSource object "implies" the specified CodeSource.
195: * <P>
196: * More specifically, this method makes the following checks, in order.
197: * If any fail, it returns false. If they all succeed, it returns true.<p>
198: * <ol>
199: * <li> <i>codesource</i> must not be null.
200: * <li> If this object's certificates are not null, then all
201: * of this object's certificates must be present in <i>codesource</i>'s
202: * certificates.
203: * <li> If this object's location (getLocation()) is not null, then the
204: * following checks are made against this object's location and
205: * <i>codesource</i>'s:<p>
206: * <ol>
207: * <li> <i>codesource</i>'s location must not be null.
208: *
209: * <li> If this object's location
210: * equals <i>codesource</i>'s location, then return true.
211: *
212: * <li> This object's protocol (getLocation().getProtocol()) must be
213: * equal to <i>codesource</i>'s protocol.
214: *
215: * <li> If this object's host (getLocation().getHost()) is not null,
216: * then the SocketPermission
217: * constructed with this object's host must imply the
218: * SocketPermission constructed with <i>codesource</i>'s host.
219: *
220: * <li> If this object's port (getLocation().getPort()) is not
221: * equal to -1 (that is, if a port is specified), it must equal
222: * <i>codesource</i>'s port.
223: *
224: * <li> If this object's file (getLocation().getFile()) doesn't equal
225: * <i>codesource</i>'s file, then the following checks are made:
226: * If this object's file ends with "/-",
227: * then <i>codesource</i>'s file must start with this object's
228: * file (exclusive the trailing "-").
229: * If this object's file ends with a "/*",
230: * then <i>codesource</i>'s file must start with this object's
231: * file and must not have any further "/" separators.
232: * If this object's file doesn't end with a "/",
233: * then <i>codesource</i>'s file must match this object's
234: * file with a '/' appended.
235: *
236: * <li> If this object's reference (getLocation().getRef()) is
237: * not null, it must equal <i>codesource</i>'s reference.
238: *
239: * </ol>
240: * </ol>
241: * <p>
242: * For example, the codesource objects with the following locations
243: * and null certificates all imply
244: * the codesource with the location "http://java.sun.com/classes/foo.jar"
245: * and null certificates:
246: * <pre>
247: * http:
248: * http://*.sun.com/classes/*
249: * http://java.sun.com/classes/-
250: * http://java.sun.com/classes/foo.jar
251: * </pre>
252: *
253: * Note that if this CodeSource has a null location and a null
254: * certificate chain, then it implies every other CodeSource.
255: *
256: * @param codesource CodeSource to compare against.
257: *
258: * @return true if the specified codesource is implied by this codesource,
259: * false if not.
260: */
261:
262: public boolean implies(CodeSource codesource) {
263: if (codesource == null)
264: return false;
265:
266: return matchCerts(codesource) && matchLocation(codesource);
267: }
268:
269: /**
270: * Returns true if all the certs in this
271: * CodeSource are also in <i>that</i>.
272: *
273: * @param that the CodeSource to check against.
274: */
275: private boolean matchCerts(CodeSource that) {
276: // match any key
277: if (this .certs == null)
278: return true;
279:
280: // if certs are null, and this.certs is not null, return false
281: if (that.certs == null)
282: return false;
283:
284: boolean match;
285: for (int i = 0; i < this .certs.length; i++) {
286: match = false;
287: for (int j = 0; j < that.certs.length; j++) {
288: if (this .certs[i].equals(that.certs[j])) {
289: match = true;
290: break;
291: }
292: }
293: if (!match)
294: return false;
295: }
296: return true;
297: }
298:
299: /**
300: * Returns true if two CodeSource's have the "same" location.
301: *
302: * @param that CodeSource to compare against
303: */
304: private boolean matchLocation(CodeSource that) {
305: if (location == null) {
306: return true;
307: }
308:
309: if ((that == null) || (that.location == null))
310: return false;
311:
312: if (location.equals(that.location))
313: return true;
314:
315: if (!location.getProtocol().equals(that.location.getProtocol()))
316: return false;
317:
318: if ((location.getHost() != null)) {
319: if ((location.getHost().equals("") || location.getHost()
320: .equals("localhost"))
321: && (that.location.getHost().equals("") || that.location
322: .getHost().equals("localhost"))) {
323: // ok
324: } else if (!location.getHost().equals(
325: that.location.getHost())) {
326: if (this .sp == null) {
327: this .sp = new SocketPermission(location.getHost(),
328: "resolve");
329: }
330: if (that.sp == null) {
331: if (that.location.getHost() == null
332: || that.location.getHost().equals(""))
333: return false;
334: that.sp = new SocketPermission(that.location
335: .getHost(), "resolve");
336: }
337:
338: boolean ok = this .sp.implies(that.sp);
339: if (!ok)
340: return false;
341: }
342: }
343:
344: if (location.getPort() != -1) {
345: if (location.getPort() != that.location.getPort())
346: return false;
347: }
348:
349: if (location.getFile().endsWith("/-")) {
350: // Matches the directory and (recursively) all files
351: // and subdirectories contained in that directory.
352: // For example, "/a/b/-" implies anything that starts with
353: // "/a/b/"
354: String this Path = location.getFile().substring(0,
355: location.getFile().length() - 1);
356: if (!that.location.getFile().startsWith(this Path))
357: return false;
358: } else if (location.getFile().endsWith("/*")) {
359: // Matches the directory and all the files contained in that
360: // directory.
361: // For example, "/a/b/*" implies anything that starts with
362: // "/a/b/" but has no further slashes
363: int last = that.location.getFile().lastIndexOf('/');
364: if (last == -1)
365: return false;
366: String this Path = location.getFile().substring(0,
367: location.getFile().length() - 1);
368: String thatPath = that.location.getFile().substring(0,
369: last + 1);
370: if (!thatPath.equals(this Path))
371: return false;
372: } else {
373: // Exact matches only.
374: // For example, "/a/b" and "/a/b/" both imply "/a/b/"
375: if ((!that.location.getFile().equals(location.getFile()))
376: && (!that.location.getFile().equals(
377: location.getFile() + "/"))) {
378: return false;
379: }
380: }
381:
382: if (location.getRef() == null)
383: return true;
384: else
385: return location.getRef().equals(that.location.getRef());
386: }
387:
388: /**
389: * Returns a string describing this CodeSource, telling its
390: * URL and certificates.
391: *
392: * @return information about this CodeSource.
393: */
394: public String toString() {
395: StringBuffer sb = new StringBuffer();
396: sb.append("(");
397: sb.append(this .location);
398: if (this .certs != null && this .certs.length > 0) {
399: for (int i = 0; i < this .certs.length; i++) {
400: sb.append(" " + this .certs[i]);
401: }
402: } else {
403: sb.append(" <no certificates>");
404: }
405: sb.append(")");
406: return sb.toString();
407: }
408:
409: /**
410: * Writes this object out to a stream (i.e., serializes it).
411: *
412: * @serialData An initial <code>URL</code> is followed by an
413: * <code>int</code> indicating the number of certificates to follow
414: * (a value of "zero" denotes that there are no certificates associated
415: * with this object).
416: * Each certificate is written out starting with a <code>String</code>
417: * denoting the certificate type, followed by an
418: * <code>int</code> specifying the length of the certificate encoding,
419: * followed by the certificate encoding itself which is written out as an
420: * array of bytes.
421: */
422: private synchronized void writeObject(java.io.ObjectOutputStream oos)
423: throws IOException {
424: oos.defaultWriteObject();
425:
426: if (certs == null || certs.length == 0) {
427: oos.writeInt(0);
428: } else {
429: // write out the total number of certs
430: oos.writeInt(certs.length);
431: // write out each cert, including its type
432: for (int i = 0; i < certs.length; i++) {
433: java.security.cert.Certificate cert = certs[i];
434: try {
435: oos.writeUTF(cert.getType());
436: byte[] encoded = cert.getEncoded();
437: oos.writeInt(encoded.length);
438: oos.write(encoded);
439: } catch (CertificateEncodingException cee) {
440: throw new IOException(cee.getMessage());
441: }
442: }
443: }
444: }
445:
446: /**
447: * Restores this object from a stream (i.e., deserializes it).
448: */
449: private synchronized void readObject(java.io.ObjectInputStream ois)
450: throws IOException, ClassNotFoundException {
451: CertificateFactory cf;
452: Hashtable cfs = null;
453:
454: ois.defaultReadObject();
455:
456: // process any new-style certs in the stream (if present)
457: int size = ois.readInt();
458: if (size > 0) {
459: // we know of 3 different cert types: X.509, PGP, SDSI, which
460: // could all be present in the stream at the same time
461: cfs = new Hashtable(3);
462: this .certs = new java.security.cert.Certificate[size];
463: }
464:
465: for (int i = 0; i < size; i++) {
466: // read the certificate type, and instantiate a certificate
467: // factory of that type (reuse existing factory if possible)
468: String certType = ois.readUTF();
469: if (cfs.containsKey(certType)) {
470: // reuse certificate factory
471: cf = (CertificateFactory) cfs.get(certType);
472: } else {
473: // create new certificate factory
474: try {
475: cf = CertificateFactory.getInstance(certType);
476: } catch (CertificateException ce) {
477: throw new ClassNotFoundException(
478: "Certificate factory for " + certType
479: + " not found");
480: }
481: // store the certificate factory so we can reuse it later
482: cfs.put(certType, cf);
483: }
484: // parse the certificate
485: byte[] encoded = null;
486: try {
487: encoded = new byte[ois.readInt()];
488: } catch (OutOfMemoryError oome) {
489: throw new IOException("Certificate too big");
490: }
491: ois.readFully(encoded);
492: ByteArrayInputStream bais = new ByteArrayInputStream(
493: encoded);
494: try {
495: this .certs[i] = cf.generateCertificate(bais);
496: } catch (CertificateException ce) {
497: throw new IOException(ce.getMessage());
498: }
499: bais.close();
500: }
501: }
502: }
|