View Javadoc
1   /*
2    * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package java.security;
27  
28  
29  import java.net.URL;
30  import java.net.SocketPermission;
31  import java.util.ArrayList;
32  import java.util.List;
33  import java.util.Hashtable;
34  import java.io.ByteArrayInputStream;
35  import java.io.IOException;
36  import java.security.cert.*;
37  
38  /**
39   *
40   * <p>This class extends the concept of a codebase to
41   * encapsulate not only the location (URL) but also the certificate chains
42   * that were used to verify signed code originating from that location.
43   *
44   * @author Li Gong
45   * @author Roland Schemers
46   */
47  
48  public class CodeSource implements java.io.Serializable {
49  
50      private static final long serialVersionUID = 4977541819976013951L;
51  
52      /**
53       * The code location.
54       *
55       * @serial
56       */
57      private URL location;
58  
59      /*
60       * The code signers.
61       */
62      private transient CodeSigner[] signers = null;
63  
64      /*
65       * The code signers. Certificate chains are concatenated.
66       */
67      private transient java.security.cert.Certificate certs[] = null;
68  
69      // cached SocketPermission used for matchLocation
70      private transient SocketPermission sp;
71  
72      // for generating cert paths
73      private transient CertificateFactory factory = null;
74  
75      /**
76       * Constructs a CodeSource and associates it with the specified
77       * location and set of certificates.
78       *
79       * @param url the location (URL).
80       *
81       * @param certs the certificate(s). It may be null. The contents of the
82       * array are copied to protect against subsequent modification.
83       */
84      public CodeSource(URL url, java.security.cert.Certificate certs[]) {
85          this.location = url;
86  
87          // Copy the supplied certs
88          if (certs != null) {
89              this.certs = certs.clone();
90          }
91      }
92  
93      /**
94       * Constructs a CodeSource and associates it with the specified
95       * location and set of code signers.
96       *
97       * @param url the location (URL).
98       * @param signers the code signers. It may be null. The contents of the
99       * array are copied to protect against subsequent modification.
100      *
101      * @since 1.5
102      */
103     public CodeSource(URL url, CodeSigner[] signers) {
104         this.location = url;
105 
106         // Copy the supplied signers
107         if (signers != null) {
108             this.signers = signers.clone();
109         }
110     }
111 
112     /**
113      * Returns the hash code value for this object.
114      *
115      * @return a hash code value for this object.
116      */
117     @Override
118     public int hashCode() {
119         if (location != null)
120             return location.hashCode();
121         else
122             return 0;
123     }
124 
125     /**
126      * Tests for equality between the specified object and this
127      * object. Two CodeSource objects are considered equal if their
128      * locations are of identical value and if their signer certificate
129      * chains are of identical value. It is not required that
130      * the certificate chains be in the same order.
131      *
132      * @param obj the object to test for equality with this object.
133      *
134      * @return true if the objects are considered equal, false otherwise.
135      */
136     @Override
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) return false;
151         } else {
152             // if location is not null, then it must equal cs.location
153             if (!location.equals(cs.location)) return false;
154         }
155 
156         // certs must match
157         return matchCerts(cs, true);
158     }
159 
160     /**
161      * Returns the location associated with this CodeSource.
162      *
163      * @return the location (URL).
164      */
165     public final URL getLocation() {
166         /* since URL is practically immutable, returning itself is not
167            a security problem */
168         return this.location;
169     }
170 
171     /**
172      * Returns the certificates associated with this CodeSource.
173      * <p>
174      * If this CodeSource object was created using the
175      * {@link #CodeSource(URL url, CodeSigner[] signers)}
176      * constructor then its certificate chains are extracted and used to
177      * create an array of Certificate objects. Each signer certificate is
178      * followed by its supporting certificate chain (which may be empty).
179      * Each signer certificate and its supporting certificate chain is ordered
180      * bottom-to-top (i.e., with the signer certificate first and the (root)
181      * certificate authority last).
182      *
183      * @return A copy of the certificates array, or null if there is none.
184      */
185     public final java.security.cert.Certificate[] getCertificates() {
186         if (certs != null) {
187             return certs.clone();
188 
189         } else if (signers != null) {
190             // Convert the code signers to certs
191             ArrayList<java.security.cert.Certificate> certChains =
192                         new ArrayList<>();
193             for (int i = 0; i < signers.length; i++) {
194                 certChains.addAll(
195                     signers[i].getSignerCertPath().getCertificates());
196             }
197             certs = certChains.toArray(
198                         new java.security.cert.Certificate[certChains.size()]);
199             return certs.clone();
200 
201         } else {
202             return null;
203         }
204     }
205 
206     /**
207      * Returns the code signers associated with this CodeSource.
208      * <p>
209      * If this CodeSource object was created using the
210      * {@link #CodeSource(URL url, java.security.cert.Certificate[] certs)}
211      * constructor then its certificate chains are extracted and used to
212      * create an array of CodeSigner objects. Note that only X.509 certificates
213      * are examined - all other certificate types are ignored.
214      *
215      * @return A copy of the code signer array, or null if there is none.
216      *
217      * @since 1.5
218      */
219     public final CodeSigner[] getCodeSigners() {
220         if (signers != null) {
221             return signers.clone();
222 
223         } else if (certs != null) {
224             // Convert the certs to code signers
225             signers = convertCertArrayToSignerArray(certs);
226             return signers.clone();
227 
228         } else {
229             return null;
230         }
231     }
232 
233     /**
234      * Returns true if this CodeSource object "implies" the specified CodeSource.
235      * <p>
236      * More specifically, this method makes the following checks.
237      * If any fail, it returns false. If they all succeed, it returns true.
238      * <ul>
239      * <li> <i>codesource</i> must not be null.
240      * <li> If this object's certificates are not null, then all
241      * of this object's certificates must be present in <i>codesource</i>'s
242      * certificates.
243      * <li> If this object's location (getLocation()) is not null, then the
244      * following checks are made against this object's location and
245      * <i>codesource</i>'s:
246      *   <ul>
247      *     <li>  <i>codesource</i>'s location must not be null.
248      *
249      *     <li>  If this object's location
250      *           equals <i>codesource</i>'s location, then return true.
251      *
252      *     <li>  This object's protocol (getLocation().getProtocol()) must be
253      *           equal to <i>codesource</i>'s protocol, ignoring case.
254      *
255      *     <li>  If this object's host (getLocation().getHost()) is not null,
256      *           then the SocketPermission
257      *           constructed with this object's host must imply the
258      *           SocketPermission constructed with <i>codesource</i>'s host.
259      *
260      *     <li>  If this object's port (getLocation().getPort()) is not
261      *           equal to -1 (that is, if a port is specified), it must equal
262      *           <i>codesource</i>'s port or default port
263      *           (codesource.getLocation().getDefaultPort()).
264      *
265      *     <li>  If this object's file (getLocation().getFile()) doesn't equal
266      *           <i>codesource</i>'s file, then the following checks are made:
267      *           If this object's file ends with "/-",
268      *           then <i>codesource</i>'s file must start with this object's
269      *           file (exclusive the trailing "-").
270      *           If this object's file ends with a "/*",
271      *           then <i>codesource</i>'s file must start with this object's
272      *           file and must not have any further "/" separators.
273      *           If this object's file doesn't end with a "/",
274      *           then <i>codesource</i>'s file must match this object's
275      *           file with a '/' appended.
276      *
277      *     <li>  If this object's reference (getLocation().getRef()) is
278      *           not null, it must equal <i>codesource</i>'s reference.
279      *
280      *   </ul>
281      * </ul>
282      * <p>
283      * For example, the codesource objects with the following locations
284      * and null certificates all imply
285      * the codesource with the location "http://java.sun.com/classes/foo.jar"
286      * and null certificates:
287      * <pre>
288      *     http:
289      *     http://*.sun.com/classes/*
290      *     http://java.sun.com/classes/-
291      *     http://java.sun.com/classes/foo.jar
292      * </pre>
293      *
294      * Note that if this CodeSource has a null location and a null
295      * certificate chain, then it implies every other CodeSource.
296      *
297      * @param codesource CodeSource to compare against.
298      *
299      * @return true if the specified codesource is implied by this codesource,
300      * false if not.
301      */
302 
303     public boolean implies(CodeSource codesource)
304     {
305         if (codesource == null)
306             return false;
307 
308         return matchCerts(codesource, false) && matchLocation(codesource);
309     }
310 
311     /**
312      * Returns true if all the certs in this
313      * CodeSource are also in <i>that</i>.
314      *
315      * @param that the CodeSource to check against.
316      * @param strict If true then a strict equality match is performed.
317      *               Otherwise a subset match is performed.
318      */
319     private boolean matchCerts(CodeSource that, boolean strict)
320     {
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) return false;
344             }
345             return true;
346 
347         // both have certs
348         } else if (certs != null && that.certs != null) {
349             if (strict && certs.length != that.certs.length) {
350                 return false;
351             }
352             for (int i = 0; i < certs.length; i++) {
353                 match = false;
354                 for (int j = 0; j < that.certs.length; j++) {
355                     if (certs[i].equals(that.certs[j])) {
356                         match = true;
357                         break;
358                     }
359                 }
360                 if (!match) return false;
361             }
362             return true;
363         }
364 
365         return false;
366     }
367 
368 
369     /**
370      * Returns true if two CodeSource's have the "same" location.
371      *
372      * @param that CodeSource to compare against
373      */
374     private boolean matchLocation(CodeSource that) {
375         if (location == null)
376             return true;
377 
378         if ((that == null) || (that.location == null))
379             return false;
380 
381         if (location.equals(that.location))
382             return true;
383 
384         if (!location.getProtocol().equalsIgnoreCase(that.location.getProtocol()))
385             return false;
386 
387         int thisPort = location.getPort();
388         if (thisPort != -1) {
389             int thatPort = that.location.getPort();
390             int port = thatPort != -1 ? thatPort
391                                       : that.location.getDefaultPort();
392             if (thisPort != port)
393                 return false;
394         }
395 
396         if (location.getFile().endsWith("/-")) {
397             // Matches the directory and (recursively) all files
398             // and subdirectories contained in that directory.
399             // For example, "/a/b/-" implies anything that starts with
400             // "/a/b/"
401             String thisPath = location.getFile().substring(0,
402                                             location.getFile().length()-1);
403             if (!that.location.getFile().startsWith(thisPath))
404                 return false;
405         } else if (location.getFile().endsWith("/*")) {
406             // Matches the directory and all the files contained in that
407             // directory.
408             // For example, "/a/b/*" implies anything that starts with
409             // "/a/b/" but has no further slashes
410             int last = that.location.getFile().lastIndexOf('/');
411             if (last == -1)
412                 return false;
413             String thisPath = location.getFile().substring(0,
414                                             location.getFile().length()-1);
415             String thatPath = that.location.getFile().substring(0, last+1);
416             if (!thatPath.equals(thisPath))
417                 return false;
418         } else {
419             // Exact matches only.
420             // For example, "/a/b" and "/a/b/" both imply "/a/b/"
421             if ((!that.location.getFile().equals(location.getFile()))
422                 && (!that.location.getFile().equals(location.getFile()+"/"))) {
423                 return false;
424             }
425         }
426 
427         if (location.getRef() != null
428             && !location.getRef().equals(that.location.getRef())) {
429             return false;
430         }
431 
432         String thisHost = location.getHost();
433         String thatHost = that.location.getHost();
434         if (thisHost != null) {
435             if (("".equals(thisHost) || "localhost".equals(thisHost)) &&
436                 ("".equals(thatHost) || "localhost".equals(thatHost))) {
437                 // ok
438             } else if (!thisHost.equals(thatHost)) {
439                 if (thatHost == null) {
440                     return false;
441                 }
442                 if (this.sp == null) {
443                     this.sp = new SocketPermission(thisHost, "resolve");
444                 }
445                 if (that.sp == null) {
446                     that.sp = new SocketPermission(thatHost, "resolve");
447                 }
448                 if (!this.sp.implies(that.sp)) {
449                     return false;
450                 }
451             }
452         }
453         // everything matches
454         return true;
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     @Override
464     public String toString() {
465         StringBuilder sb = new StringBuilder();
466         sb.append("(");
467         sb.append(this.location);
468 
469         if (this.certs != null && this.certs.length > 0) {
470             for (int i = 0; i < this.certs.length; i++) {
471                 sb.append( " " + this.certs[i]);
472             }
473 
474         } else if (this.signers != null && this.signers.length > 0) {
475             for (int i = 0; i < this.signers.length; i++) {
476                 sb.append( " " + this.signers[i]);
477             }
478         } else {
479             sb.append(" <no signer certificates>");
480         }
481         sb.append(")");
482         return sb.toString();
483     }
484 
485     /**
486      * Writes this object out to a stream (i.e., serializes it).
487      *
488      * @serialData An initial {@code URL} is followed by an
489      * {@code int} indicating the number of certificates to follow
490      * (a value of "zero" denotes that there are no certificates associated
491      * with this object).
492      * Each certificate is written out starting with a {@code String}
493      * denoting the certificate type, followed by an
494      * {@code int} specifying the length of the certificate encoding,
495      * followed by the certificate encoding itself which is written out as an
496      * array of bytes. Finally, if any code signers are present then the array
497      * of code signers is serialized and written out too.
498      */
499     private void writeObject(java.io.ObjectOutputStream oos)
500         throws IOException
501     {
502         oos.defaultWriteObject(); // location
503 
504         // Serialize the array of certs
505         if (certs == null || certs.length == 0) {
506             oos.writeInt(0);
507         } else {
508             // write out the total number of certs
509             oos.writeInt(certs.length);
510             // write out each cert, including its type
511             for (int i = 0; i < certs.length; i++) {
512                 java.security.cert.Certificate cert = certs[i];
513                 try {
514                     oos.writeUTF(cert.getType());
515                     byte[] encoded = cert.getEncoded();
516                     oos.writeInt(encoded.length);
517                     oos.write(encoded);
518                 } catch (CertificateEncodingException cee) {
519                     throw new IOException(cee.getMessage());
520                 }
521             }
522         }
523 
524         // Serialize the array of code signers (if any)
525         if (signers != null && signers.length > 0) {
526             oos.writeObject(signers);
527         }
528     }
529 
530     /**
531      * Restores this object from a stream (i.e., deserializes it).
532      */
533     private void readObject(java.io.ObjectInputStream ois)
534         throws IOException, ClassNotFoundException
535     {
536         CertificateFactory cf;
537         Hashtable<String, CertificateFactory> cfs = null;
538 
539         ois.defaultReadObject(); // location
540 
541         // process any new-style certs in the stream (if present)
542         int size = ois.readInt();
543         if (size > 0) {
544             // we know of 3 different cert types: X.509, PGP, SDSI, which
545             // could all be present in the stream at the same time
546             cfs = new Hashtable<String, CertificateFactory>(3);
547             this.certs = new java.security.cert.Certificate[size];
548         }
549 
550         for (int i = 0; i < size; i++) {
551             // read the certificate type, and instantiate a certificate
552             // factory of that type (reuse existing factory if possible)
553             String certType = ois.readUTF();
554             if (cfs.containsKey(certType)) {
555                 // reuse certificate factory
556                 cf = cfs.get(certType);
557             } else {
558                 // create new certificate factory
559                 try {
560                     cf = CertificateFactory.getInstance(certType);
561                 } catch (CertificateException ce) {
562                     throw new ClassNotFoundException
563                         ("Certificate factory for " + certType + " not found");
564                 }
565                 // store the certificate factory so we can reuse it later
566                 cfs.put(certType, cf);
567             }
568             // parse the certificate
569             byte[] encoded = null;
570             try {
571                 encoded = new byte[ois.readInt()];
572             } catch (OutOfMemoryError oome) {
573                 throw new IOException("Certificate too big");
574             }
575             ois.readFully(encoded);
576             ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
577             try {
578                 this.certs[i] = cf.generateCertificate(bais);
579             } catch (CertificateException ce) {
580                 throw new IOException(ce.getMessage());
581             }
582             bais.close();
583         }
584 
585         // Deserialize array of code signers (if any)
586         try {
587             this.signers = ((CodeSigner[])ois.readObject()).clone();
588         } catch (IOException ioe) {
589             // no signers present
590         }
591     }
592 
593     /*
594      * Convert an array of certificates to an array of code signers.
595      * The array of certificates is a concatenation of certificate chains
596      * where the initial certificate in each chain is the end-entity cert.
597      *
598      * @return An array of code signers or null if none are generated.
599      */
600     private CodeSigner[] convertCertArrayToSignerArray(
601         java.security.cert.Certificate[] certs) {
602 
603         if (certs == null) {
604             return null;
605         }
606 
607         try {
608             // Initialize certificate factory
609             if (factory == null) {
610                 factory = CertificateFactory.getInstance("X.509");
611             }
612 
613             // Iterate through all the certificates
614             int i = 0;
615             List<CodeSigner> signers = new ArrayList<>();
616             while (i < certs.length) {
617                 List<java.security.cert.Certificate> certChain =
618                         new ArrayList<>();
619                 certChain.add(certs[i++]); // first cert is an end-entity cert
620                 int j = i;
621 
622                 // Extract chain of certificates
623                 // (loop while certs are not end-entity certs)
624                 while (j < certs.length &&
625                     certs[j] instanceof X509Certificate &&
626                     ((X509Certificate)certs[j]).getBasicConstraints() != -1) {
627                     certChain.add(certs[j]);
628                     j++;
629                 }
630                 i = j;
631                 CertPath certPath = factory.generateCertPath(certChain);
632                 signers.add(new CodeSigner(certPath, null));
633             }
634 
635             if (signers.isEmpty()) {
636                 return null;
637             } else {
638                 return signers.toArray(new CodeSigner[signers.size()]);
639             }
640 
641         } catch (CertificateException e) {
642             return null; //TODO - may be better to throw an ex. here
643         }
644     }
645 }