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