001: /*
002: * Copyright 1997-2006 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 java.security;
027:
028: import java.net.URL;
029: import java.net.SocketPermission;
030: import java.util.ArrayList;
031: import java.util.List;
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 chains
041: * that were used to verify signed code originating from that location.
042: *
043: * @version 1.48, 05/05/07
044: * @author Li Gong
045: * @author Roland Schemers
046: */
047:
048: public class CodeSource implements java.io.Serializable {
049:
050: private static final long serialVersionUID = 4977541819976013951L;
051:
052: /**
053: * The code location.
054: *
055: * @serial
056: */
057: private URL location;
058:
059: /*
060: * The code signers.
061: */
062: private transient CodeSigner[] signers = null;
063:
064: /*
065: * The code signers. Certificate chains are concatenated.
066: */
067: private transient java.security.cert.Certificate certs[] = null;
068:
069: // cached SocketPermission used for matchLocation
070: private transient SocketPermission sp;
071:
072: // for generating cert paths
073: private transient CertificateFactory factory = null;
074:
075: /**
076: * Constructs a CodeSource and associates it with the specified
077: * location and set of certificates.
078: *
079: * @param url the location (URL).
080: *
081: * @param certs the certificate(s). It may be null. The contents of the
082: * array are copied to protect against subsequent modification.
083: */
084: public CodeSource(URL url, java.security.cert.Certificate certs[]) {
085: this .location = url;
086:
087: // Copy the supplied certs
088: if (certs != null) {
089: this .certs = (java.security.cert.Certificate[]) certs
090: .clone();
091: }
092: }
093:
094: /**
095: * Constructs a CodeSource and associates it with the specified
096: * location and set of code signers.
097: *
098: * @param url the location (URL).
099: * @param signers the code signers. It may be null. The contents of the
100: * array are copied to protect against subsequent modification.
101: *
102: * @since 1.5
103: */
104: public CodeSource(URL url, CodeSigner[] signers) {
105: this .location = url;
106:
107: // Copy the supplied signers
108: if (signers != null) {
109: this .signers = (CodeSigner[]) signers.clone();
110: }
111: }
112:
113: /**
114: * Returns the hash code value for this object.
115: *
116: * @return a hash code value for this object.
117: */
118:
119: public int hashCode() {
120: if (location != null)
121: return location.hashCode();
122: else
123: return 0;
124: }
125:
126: /**
127: * Tests for equality between the specified object and this
128: * object. Two CodeSource objects are considered equal if their
129: * locations are of identical value and if their signer certificate
130: * chains are of identical value. It is not required that
131: * the certificate chains be in the same order.
132: *
133: * @param obj the object to test for equality with this object.
134: *
135: * @return true if the objects are considered equal, false otherwise.
136: */
137: public boolean equals(Object obj) {
138: if (obj == this )
139: return true;
140:
141: // objects types must be equal
142: if (!(obj instanceof CodeSource))
143: return false;
144:
145: CodeSource cs = (CodeSource) obj;
146:
147: // URLs must match
148: if (location == null) {
149: // if location is null, then cs.location must be null as well
150: if (cs.location != null)
151: return false;
152: } else {
153: // if location is not null, then it must equal cs.location
154: if (!location.equals(cs.location))
155: return false;
156: }
157:
158: // certs must match
159: return matchCerts(cs, true);
160: }
161:
162: /**
163: * Returns the location associated with this CodeSource.
164: *
165: * @return the location (URL).
166: */
167: public final URL getLocation() {
168: /* since URL is practically immutable, returning itself is not
169: a security problem */
170: return this .location;
171: }
172:
173: /**
174: * Returns the certificates associated with this CodeSource.
175: * <p>
176: * If this CodeSource object was created using the
177: * {@link #CodeSource(URL url, CodeSigner[] signers)}
178: * constructor then its certificate chains are extracted and used to
179: * create an array of Certificate objects. Each signer certificate is
180: * followed by its supporting certificate chain (which may be empty).
181: * Each signer certificate and its supporting certificate chain is ordered
182: * bottom-to-top (i.e., with the signer certificate first and the (root)
183: * certificate authority last).
184: *
185: * @return A copy of the certificates array, or null if there is none.
186: */
187: public final java.security.cert.Certificate[] getCertificates() {
188: if (certs != null) {
189: return (java.security.cert.Certificate[]) certs.clone();
190:
191: } else if (signers != null) {
192: // Convert the code signers to certs
193: ArrayList<java.security.cert.Certificate> certChains = new ArrayList<java.security.cert.Certificate>();
194: for (int i = 0; i < signers.length; i++) {
195: certChains.addAll(signers[i].getSignerCertPath()
196: .getCertificates());
197: }
198: certs = certChains
199: .toArray(new java.security.cert.Certificate[certChains
200: .size()]);
201: return certs.clone();
202:
203: } else {
204: return null;
205: }
206: }
207:
208: /**
209: * Returns the code signers associated with this CodeSource.
210: * <p>
211: * If this CodeSource object was created using the
212: * {@link #CodeSource(URL url, Certificate[] certs)}
213: * constructor then its certificate chains are extracted and used to
214: * create an array of CodeSigner objects. Note that only X.509 certificates
215: * are examined - all other certificate types are ignored.
216: *
217: * @return A copy of the code signer array, or null if there is none.
218: *
219: * @since 1.5
220: */
221: public final CodeSigner[] getCodeSigners() {
222: if (signers != null) {
223: return (CodeSigner[]) signers.clone();
224:
225: } else if (certs != null) {
226: // Convert the certs to code signers
227: signers = convertCertArrayToSignerArray(certs);
228: return (CodeSigner[]) signers.clone();
229:
230: } else {
231: return null;
232: }
233: }
234:
235: /**
236: * Returns true if this CodeSource object "implies" the specified CodeSource.
237: * <P>
238: * More specifically, this method makes the following checks, in order.
239: * If any fail, it returns false. If they all succeed, it returns true.<p>
240: * <ol>
241: * <li> <i>codesource</i> must not be null.
242: * <li> If this object's certificates are not null, then all
243: * of this object's certificates must be present in <i>codesource</i>'s
244: * certificates.
245: * <li> If this object's location (getLocation()) is not null, then the
246: * following checks are made against this object's location and
247: * <i>codesource</i>'s:<p>
248: * <ol>
249: * <li> <i>codesource</i>'s location must not be null.
250: *
251: * <li> If this object's location
252: * equals <i>codesource</i>'s location, then return true.
253: *
254: * <li> This object's protocol (getLocation().getProtocol()) must be
255: * equal to <i>codesource</i>'s protocol.
256: *
257: * <li> If this object's host (getLocation().getHost()) is not null,
258: * then the SocketPermission
259: * constructed with this object's host must imply the
260: * SocketPermission constructed with <i>codesource</i>'s host.
261: *
262: * <li> If this object's port (getLocation().getPort()) is not
263: * equal to -1 (that is, if a port is specified), it must equal
264: * <i>codesource</i>'s port.
265: *
266: * <li> If this object's file (getLocation().getFile()) doesn't equal
267: * <i>codesource</i>'s file, then the following checks are made:
268: * If this object's file ends with "/-",
269: * then <i>codesource</i>'s file must start with this object's
270: * file (exclusive the trailing "-").
271: * If this object's file ends with a "/*",
272: * then <i>codesource</i>'s file must start with this object's
273: * file and must not have any further "/" separators.
274: * If this object's file doesn't end with a "/",
275: * then <i>codesource</i>'s file must match this object's
276: * file with a '/' appended.
277: *
278: * <li> If this object's reference (getLocation().getRef()) is
279: * not null, it must equal <i>codesource</i>'s reference.
280: *
281: * </ol>
282: * </ol>
283: * <p>
284: * For example, the codesource objects with the following locations
285: * and null certificates all imply
286: * the codesource with the location "http://java.sun.com/classes/foo.jar"
287: * and null certificates:
288: * <pre>
289: * http:
290: * http://*.sun.com/classes/*
291: * http://java.sun.com/classes/-
292: * http://java.sun.com/classes/foo.jar
293: * </pre>
294: *
295: * Note that if this CodeSource has a null location and a null
296: * certificate chain, then it implies every other CodeSource.
297: *
298: * @param codesource CodeSource to compare against.
299: *
300: * @return true if the specified codesource is implied by this codesource,
301: * false if not.
302: */
303:
304: public boolean implies(CodeSource codesource) {
305: if (codesource == null)
306: return false;
307:
308: return matchCerts(codesource, false)
309: && matchLocation(codesource);
310: }
311:
312: /**
313: * Returns true if all the certs in this
314: * CodeSource are also in <i>that</i>.
315: *
316: * @param that the CodeSource to check against.
317: * @param strict If true then a strict equality match is performed.
318: * Otherwise a subset match is performed.
319: */
320: private boolean matchCerts(CodeSource that, boolean strict) {
321: boolean match;
322:
323: // match any key
324: if (certs == null && signers == null) {
325: if (strict) {
326: return (that.certs == null && that.signers == null);
327: } else {
328: return true;
329: }
330: // both have signers
331: } else if (signers != null && that.signers != null) {
332: if (strict && signers.length != that.signers.length) {
333: return false;
334: }
335: for (int i = 0; i < signers.length; i++) {
336: match = false;
337: for (int j = 0; j < that.signers.length; j++) {
338: if (signers[i].equals(that.signers[j])) {
339: match = true;
340: break;
341: }
342: }
343: if (!match)
344: return false;
345: }
346: return true;
347:
348: // both have certs
349: } else if (certs != null && that.certs != null) {
350: if (strict && certs.length != that.certs.length) {
351: return false;
352: }
353: for (int i = 0; i < certs.length; i++) {
354: match = false;
355: for (int j = 0; j < that.certs.length; j++) {
356: if (certs[i].equals(that.certs[j])) {
357: match = true;
358: break;
359: }
360: }
361: if (!match)
362: return false;
363: }
364: return true;
365: }
366:
367: return false;
368: }
369:
370: /**
371: * Returns true if two CodeSource's have the "same" location.
372: *
373: * @param that CodeSource to compare against
374: */
375: private boolean matchLocation(CodeSource that) {
376: if (location == null) {
377: return true;
378: }
379:
380: if ((that == null) || (that.location == null))
381: return false;
382:
383: if (location.equals(that.location))
384: return true;
385:
386: if (!location.getProtocol().equals(that.location.getProtocol()))
387: return false;
388:
389: String this Host = location.getHost();
390: String thatHost = that.location.getHost();
391:
392: if (this Host != null) {
393: if (("".equals(this Host) || "localhost".equals(this Host))
394: && ("".equals(thatHost) || "localhost"
395: .equals(thatHost))) {
396: // ok
397: } else if (!this Host.equals(thatHost)) {
398: if (thatHost == null) {
399: return false;
400: }
401: if (this .sp == null) {
402: this .sp = new SocketPermission(this Host, "resolve");
403: }
404: if (that.sp == null) {
405: that.sp = new SocketPermission(thatHost, "resolve");
406: }
407: if (!this .sp.implies(that.sp)) {
408: return false;
409: }
410: }
411: }
412:
413: if (location.getPort() != -1) {
414: if (location.getPort() != that.location.getPort())
415: return false;
416: }
417:
418: if (location.getFile().endsWith("/-")) {
419: // Matches the directory and (recursively) all files
420: // and subdirectories contained in that directory.
421: // For example, "/a/b/-" implies anything that starts with
422: // "/a/b/"
423: String this Path = location.getFile().substring(0,
424: location.getFile().length() - 1);
425: if (!that.location.getFile().startsWith(this Path))
426: return false;
427: } else if (location.getFile().endsWith("/*")) {
428: // Matches the directory and all the files contained in that
429: // directory.
430: // For example, "/a/b/*" implies anything that starts with
431: // "/a/b/" but has no further slashes
432: int last = that.location.getFile().lastIndexOf('/');
433: if (last == -1)
434: return false;
435: String this Path = location.getFile().substring(0,
436: location.getFile().length() - 1);
437: String thatPath = that.location.getFile().substring(0,
438: last + 1);
439: if (!thatPath.equals(this Path))
440: return false;
441: } else {
442: // Exact matches only.
443: // For example, "/a/b" and "/a/b/" both imply "/a/b/"
444: if ((!that.location.getFile().equals(location.getFile()))
445: && (!that.location.getFile().equals(
446: location.getFile() + "/"))) {
447: return false;
448: }
449: }
450:
451: if (location.getRef() == null)
452: return true;
453: else
454: return location.getRef().equals(that.location.getRef());
455: }
456:
457: /**
458: * Returns a string describing this CodeSource, telling its
459: * URL and certificates.
460: *
461: * @return information about this CodeSource.
462: */
463: public String toString() {
464: StringBuilder sb = new StringBuilder();
465: sb.append("(");
466: sb.append(this .location);
467:
468: if (this .certs != null && this .certs.length > 0) {
469: for (int i = 0; i < this .certs.length; i++) {
470: sb.append(" " + this .certs[i]);
471: }
472:
473: } else if (this .signers != null && this .signers.length > 0) {
474: for (int i = 0; i < this .signers.length; i++) {
475: sb.append(" " + this .signers[i]);
476: }
477: } else {
478: sb.append(" <no signer certificates>");
479: }
480: sb.append(")");
481: return sb.toString();
482: }
483:
484: /**
485: * Writes this object out to a stream (i.e., serializes it).
486: *
487: * @serialData An initial <code>URL</code> is followed by an
488: * <code>int</code> indicating the number of certificates to follow
489: * (a value of "zero" denotes that there are no certificates associated
490: * with this object).
491: * Each certificate is written out starting with a <code>String</code>
492: * denoting the certificate type, followed by an
493: * <code>int</code> specifying the length of the certificate encoding,
494: * followed by the certificate encoding itself which is written out as an
495: * array of bytes. Finally, if any code signers are present then the array
496: * of code signers is serialized and written out too.
497: */
498: private void writeObject(java.io.ObjectOutputStream oos)
499: throws IOException {
500: oos.defaultWriteObject(); // location
501:
502: // Serialize the array of certs
503: if (certs == null || certs.length == 0) {
504: oos.writeInt(0);
505: } else {
506: // write out the total number of certs
507: oos.writeInt(certs.length);
508: // write out each cert, including its type
509: for (int i = 0; i < certs.length; i++) {
510: java.security.cert.Certificate cert = certs[i];
511: try {
512: oos.writeUTF(cert.getType());
513: byte[] encoded = cert.getEncoded();
514: oos.writeInt(encoded.length);
515: oos.write(encoded);
516: } catch (CertificateEncodingException cee) {
517: throw new IOException(cee.getMessage());
518: }
519: }
520: }
521:
522: // Serialize the array of code signers (if any)
523: if (signers != null && signers.length > 0) {
524: oos.writeObject(signers);
525: }
526: }
527:
528: /**
529: * Restores this object from a stream (i.e., deserializes it).
530: */
531: private void readObject(java.io.ObjectInputStream ois)
532: throws IOException, ClassNotFoundException {
533: CertificateFactory cf;
534: Hashtable<String, CertificateFactory> cfs = null;
535:
536: ois.defaultReadObject(); // location
537:
538: // process any new-style certs in the stream (if present)
539: int size = ois.readInt();
540: if (size > 0) {
541: // we know of 3 different cert types: X.509, PGP, SDSI, which
542: // could all be present in the stream at the same time
543: cfs = new Hashtable<String, CertificateFactory>(3);
544: this .certs = new java.security.cert.Certificate[size];
545: }
546:
547: for (int i = 0; i < size; i++) {
548: // read the certificate type, and instantiate a certificate
549: // factory of that type (reuse existing factory if possible)
550: String certType = ois.readUTF();
551: if (cfs.containsKey(certType)) {
552: // reuse certificate factory
553: cf = cfs.get(certType);
554: } else {
555: // create new certificate factory
556: try {
557: cf = CertificateFactory.getInstance(certType);
558: } catch (CertificateException ce) {
559: throw new ClassNotFoundException(
560: "Certificate factory for " + certType
561: + " not found");
562: }
563: // store the certificate factory so we can reuse it later
564: cfs.put(certType, cf);
565: }
566: // parse the certificate
567: byte[] encoded = null;
568: try {
569: encoded = new byte[ois.readInt()];
570: } catch (OutOfMemoryError oome) {
571: throw new IOException("Certificate too big");
572: }
573: ois.readFully(encoded);
574: ByteArrayInputStream bais = new ByteArrayInputStream(
575: encoded);
576: try {
577: this .certs[i] = cf.generateCertificate(bais);
578: } catch (CertificateException ce) {
579: throw new IOException(ce.getMessage());
580: }
581: bais.close();
582: }
583:
584: // Deserialize array of code signers (if any)
585: try {
586: this .signers = (CodeSigner[]) ois.readObject();
587: } catch (IOException ioe) {
588: // no signers present
589: }
590: }
591:
592: /*
593: * Convert an array of certificates to an array of code signers.
594: * The array of certificates is a concatenation of certificate chains
595: * where the initial certificate in each chain is the end-entity cert.
596: *
597: * @return An array of code signers or null if none are generated.
598: */
599: private CodeSigner[] convertCertArrayToSignerArray(
600: java.security.cert.Certificate[] certs) {
601:
602: if (certs == null) {
603: return null;
604: }
605:
606: try {
607: // Initialize certificate factory
608: if (factory == null) {
609: factory = CertificateFactory.getInstance("X.509");
610: }
611:
612: // Iterate through all the certificates
613: int i = 0;
614: List<CodeSigner> signers = new ArrayList<CodeSigner>();
615: while (i < certs.length) {
616: List<java.security.cert.Certificate> certChain = new ArrayList<java.security.cert.Certificate>();
617: certChain.add(certs[i++]); // first cert is an end-entity cert
618: int j = i;
619:
620: // Extract chain of certificates
621: // (loop while certs are not end-entity certs)
622: while (j < certs.length
623: && certs[j] instanceof X509Certificate
624: && ((X509Certificate) certs[j])
625: .getBasicConstraints() != -1) {
626: certChain.add(certs[j]);
627: j++;
628: }
629: i = j;
630: CertPath certPath = factory.generateCertPath(certChain);
631: signers.add(new CodeSigner(certPath, null));
632: }
633:
634: if (signers.isEmpty()) {
635: return null;
636: } else {
637: return signers.toArray(new CodeSigner[signers.size()]);
638: }
639:
640: } catch (CertificateException e) {
641: return null; //TODO - may be better to throw an ex. here
642: }
643: }
644: }
|