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 gnu.java.lang.CPStringBuilder;
042    
043    import java.io.ByteArrayInputStream;
044    import java.io.IOException;
045    import java.io.ObjectInputStream;
046    import java.io.ObjectOutputStream;
047    import java.io.Serializable;
048    import java.net.SocketPermission;
049    import java.net.URL;
050    // Note that this overrides Certificate in this package.
051    import java.security.cert.Certificate;
052    import java.security.cert.CertificateEncodingException;
053    import java.security.cert.CertificateException;
054    import java.security.cert.CertificateFactory;
055    import java.util.Arrays;
056    import java.util.HashSet;
057    import 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     */
069    public 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