1 /*
2  * Copyright (c) 1997, 2017, 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 import sun.net.util.URLUtil;
38 import sun.security.util.IOUtils;
39
40 /**
41  *
42  * <p>This class extends the concept of a codebase to
43  * encapsulate not only the location (URL) but also the certificate chains
44  * that were used to verify signed code originating from that location.
45  *
46  * @author Li Gong
47  * @author Roland Schemers
48  * @since 1.2
49  */

50
51 public class CodeSource implements java.io.Serializable {
52
53     private static final long serialVersionUID = 4977541819976013951L;
54
55     /**
56      * The code location.
57      *
58      * @serial
59      */

60     private final URL location;
61
62     /*
63      * The code signers.
64      */

65     private transient CodeSigner[] signers = null;
66
67     /*
68      * The code signers. Certificate chains are concatenated.
69      */

70     private transient java.security.cert.Certificate[] certs = null;
71
72     // cached SocketPermission used for matchLocation
73     private transient SocketPermission sp;
74
75     // for generating cert paths
76     private transient CertificateFactory factory = null;
77
78     /**
79      * A String form of the URL for use as a key in HashMaps/Sets. The String
80      * form should be behave in the same manner as the URL when compared for
81      * equality in a HashMap/Set, except that no nameservice lookup is done
82      * on the hostname (only string comparison), and the fragment is not
83      * considered.
84      */

85     private transient String locationNoFragString;
86
87     /**
88      * Constructs a CodeSource and associates it with the specified
89      * location and set of certificates.
90      *
91      * @param url the location (URL).  It may be {@code null}.
92      * @param certs the certificate(s). It may be {@code null}. The contents
93      * of the array are copied to protect against subsequent modification.
94      */

95     public CodeSource(URL url, java.security.cert.Certificate[] certs) {
96         this.location = url;
97         if (url != null) {
98             this.locationNoFragString = URLUtil.urlNoFragString(url);
99         }
100
101         // Copy the supplied certs
102         if (certs != null) {
103             this.certs = certs.clone();
104         }
105     }
106
107     /**
108      * Constructs a CodeSource and associates it with the specified
109      * location and set of code signers.
110      *
111      * @param url the location (URL).  It may be {@code null}.
112      * @param signers the code signers. It may be {@code null}. The contents
113      * of the array are copied to protect against subsequent modification.
114      *
115      * @since 1.5
116      */

117     public CodeSource(URL url, CodeSigner[] signers) {
118         this.location = url;
119         if (url != null) {
120             this.locationNoFragString = URLUtil.urlNoFragString(url);
121         }
122
123         // Copy the supplied signers
124         if (signers != null) {
125             this.signers = signers.clone();
126         }
127     }
128
129     /**
130      * Returns the hash code value for this object.
131      *
132      * @return a hash code value for this object.
133      */

134     @Override
135     public int hashCode() {
136         if (location != null)
137             return location.hashCode();
138         else
139             return 0;
140     }
141
142     /**
143      * Tests for equality between the specified object and this
144      * object. Two CodeSource objects are considered equal if their
145      * locations are of identical value and if their signer certificate
146      * chains are of identical value. It is not required that
147      * the certificate chains be in the same order.
148      *
149      * @param obj the object to test for equality with this object.
150      *
151      * @return true if the objects are considered equal, false otherwise.
152      */

153     @Override
154     public boolean equals(Object obj) {
155         if (obj == this)
156             return true;
157
158         // objects types must be equal
159         if (!(obj instanceof CodeSource))
160             return false;
161
162         CodeSource cs = (CodeSource) obj;
163
164         // URLs must match
165         if (location == null) {
166             // if location is null, then cs.location must be null as well
167             if (cs.location != nullreturn false;
168         } else {
169             // if location is not null, then it must equal cs.location
170             if (!location.equals(cs.location)) return false;
171         }
172
173         // certs must match
174         return matchCerts(cs, true);
175     }
176
177     /**
178      * Returns the location associated with this CodeSource.
179      *
180      * @return the location (URL), or {@code nullif no URL was supplied
181      * during construction.
182      */

183     public final URL getLocation() {
184         /* since URL is practically immutable, returning itself is not
185            a security problem */

186         return this.location;
187     }
188
189     /**
190      * Returns a String form of the URL for use as a key in HashMaps/Sets.
191      */

192     String getLocationNoFragString() {
193         return locationNoFragString;
194     }
195
196     /**
197      * Returns the certificates associated with this CodeSource.
198      * <p>
199      * If this CodeSource object was created using the
200      * {@link #CodeSource(URL url, CodeSigner[] signers)}
201      * constructor then its certificate chains are extracted and used to
202      * create an array of Certificate objects. Each signer certificate is
203      * followed by its supporting certificate chain (which may be empty).
204      * Each signer certificate and its supporting certificate chain is ordered
205      * bottom-to-top (i.e., with the signer certificate first and the (root)
206      * certificate authority last).
207      *
208      * @return a copy of the certificate array, or {@code nullif there
209      * is none.
210      */

211     public final java.security.cert.Certificate[] getCertificates() {
212         if (certs != null) {
213             return certs.clone();
214
215         } else if (signers != null) {
216             // Convert the code signers to certs
217             ArrayList<java.security.cert.Certificate> certChains =
218                         new ArrayList<>();
219             for (int i = 0; i < signers.length; i++) {
220                 certChains.addAll(
221                     signers[i].getSignerCertPath().getCertificates());
222             }
223             certs = certChains.toArray(
224                         new java.security.cert.Certificate[certChains.size()]);
225             return certs.clone();
226
227         } else {
228             return null;
229         }
230     }
231
232     /**
233      * Returns the code signers associated with this CodeSource.
234      * <p>
235      * If this CodeSource object was created using the
236      * {@link #CodeSource(URL url, java.security.cert.Certificate[] certs)}
237      * constructor then its certificate chains are extracted and used to
238      * create an array of CodeSigner objects. Note that only X.509 certificates
239      * are examined - all other certificate types are ignored.
240      *
241      * @return a copy of the code signer array, or {@code nullif there
242      * is none.
243      *
244      * @since 1.5
245      */

246     public final CodeSigner[] getCodeSigners() {
247         if (signers != null) {
248             return signers.clone();
249
250         } else if (certs != null) {
251             // Convert the certs to code signers
252             signers = convertCertArrayToSignerArray(certs);
253             return signers.clone();
254
255         } else {
256             return null;
257         }
258     }
259
260     /**
261      * Returns true if this CodeSource object "implies" the specified CodeSource.
262      * <p>
263      * More specifically, this method makes the following checks.
264      * If any fail, it returns false. If they all succeed, it returns true.
265      * <ul>
266      * <li> <i>codesource</i> must not be null.
267      * <li> If this object's certificates are not null, then all
268      * of this object's certificates must be present in <i>codesource</i>'s
269      * certificates.
270      * <li> If this object's location (getLocation()) is not null, then the
271      * following checks are made against this object's location and
272      * <i>codesource</i>'s:
273      *   <ul>
274      *     <li>  <i>codesource</i>'s location must not be null.
275      *
276      *     <li>  If this object's location
277      *           equals <i>codesource</i>'s location, then return true.
278      *
279      *     <li>  This object's protocol (getLocation().getProtocol()) must be
280      *           equal to <i>codesource</i>'s protocol, ignoring case.
281      *
282      *     <li>  If this object's host (getLocation().getHost()) is not null,
283      *           then the SocketPermission
284      *           constructed with this object's host must imply the
285      *           SocketPermission constructed with <i>codesource</i>'s host.
286      *
287      *     <li>  If this object's port (getLocation().getPort()) is not
288      *           equal to -1 (that is, if a port is specified), it must equal
289      *           <i>codesource</i>'s port or default port
290      *           (codesource.getLocation().getDefaultPort()).
291      *
292      *     <li>  If this object's file (getLocation().getFile()) doesn't equal
293      *           <i>codesource</i>'s file, then the following checks are made:
294      *           If this object's file ends with "/-",
295      *           then <i>codesource</i>'s file must start with this object's
296      *           file (exclusive the trailing "-").
297      *           If this object's file ends with a "/*",
298      *           then <i>codesource</i>'s file must start with this object's
299      *           file and must not have any further "/" separators.
300      *           If this object's file doesn't end with a "/",
301      *           then <i>codesource</i>'s file must match this object's
302      *           file with a '/' appended.
303      *
304      *     <li>  If this object's reference (getLocation().getRef()) is
305      *           not null, it must equal <i>codesource</i>'s reference.
306      *
307      *   </ul>
308      * </ul>
309      * <p>
310      * For example, the codesource objects with the following locations
311      * and null certificates all imply
312      * the codesource with the location "http://java.sun.com/classes/foo.jar"
313      * and null certificates:
314      * <pre>
315      *     http:
316      *     http://*.sun.com/classes/*
317      *     http://java.sun.com/classes/-
318      *     http://java.sun.com/classes/foo.jar
319      * </pre>
320      *
321      * Note that if this CodeSource has a null location and a null
322      * certificate chain, then it implies every other CodeSource.
323      *
324      * @param codesource CodeSource to compare against.
325      *
326      * @return true if the specified codesource is implied by this codesource,
327      * false if not.
328      */

329     public boolean implies(CodeSource codesource)
330     {
331         if (codesource == null)
332             return false;
333
334         return matchCerts(codesource, false) && matchLocation(codesource);
335     }
336
337     /**
338      * Returns true if all the certs in this
339      * CodeSource are also in <i>that</i>.
340      *
341      * @param that the CodeSource to check against.
342      * @param strict if true then a strict equality match is performed.
343      *               Otherwise a subset match is performed.
344      */

345     boolean matchCerts(CodeSource that, boolean strict)
346     {
347         boolean match;
348
349         // match any key
350         if (certs == null && signers == null) {
351             if (strict) {
352                 return (that.certs == null && that.signers == null);
353             } else {
354                 return true;
355             }
356         // both have signers
357         } else if (signers != null && that.signers != null) {
358             if (strict && signers.length != that.signers.length) {
359                 return false;
360             }
361             for (int i = 0; i < signers.length; i++) {
362                 match = false;
363                 for (int j = 0; j < that.signers.length; j++) {
364                     if (signers[i].equals(that.signers[j])) {
365                         match = true;
366                         break;
367                     }
368                 }
369                 if (!match) return false;
370             }
371             return true;
372
373         // both have certs
374         } else if (certs != null && that.certs != null) {
375             if (strict && certs.length != that.certs.length) {
376                 return false;
377             }
378             for (int i = 0; i < certs.length; i++) {
379                 match = false;
380                 for (int j = 0; j < that.certs.length; j++) {
381                     if (certs[i].equals(that.certs[j])) {
382                         match = true;
383                         break;
384                     }
385                 }
386                 if (!match) return false;
387             }
388             return true;
389         }
390
391         return false;
392     }
393
394
395     /**
396      * Returns true if two CodeSource's have the "same" location.
397      *
398      * @param that CodeSource to compare against
399      */

400     private boolean matchLocation(CodeSource that) {
401         if (location == null)
402             return true;
403
404         if ((that == null) || (that.location == null))
405             return false;
406
407         if (location.equals(that.location))
408             return true;
409
410         if (!location.getProtocol().equalsIgnoreCase(that.location.getProtocol()))
411             return false;
412
413         int thisPort = location.getPort();
414         if (thisPort != -1) {
415             int thatPort = that.location.getPort();
416             int port = thatPort != -1 ? thatPort
417                                       : that.location.getDefaultPort();
418             if (thisPort != port)
419                 return false;
420         }
421
422         if (location.getFile().endsWith("/-")) {
423             // Matches the directory and (recursively) all files
424             // and subdirectories contained in that directory.
425             // For example, "/a/b/-" implies anything that starts with
426             // "/a/b/"
427             String thisPath = location.getFile().substring(0,
428                                             location.getFile().length()-1);
429             if (!that.location.getFile().startsWith(thisPath))
430                 return false;
431         } else if (location.getFile().endsWith("/*")) {
432             // Matches the directory and all the files contained in that
433             // directory.
434             // For example, "/a/b/*" implies anything that starts with
435             // "/a/b/" but has no further slashes
436             int last = that.location.getFile().lastIndexOf('/');
437             if (last == -1)
438                 return false;
439             String thisPath = location.getFile().substring(0,
440                                             location.getFile().length()-1);
441             String thatPath = that.location.getFile().substring(0, last+1);
442             if (!thatPath.equals(thisPath))
443                 return false;
444         } else {
445             // Exact matches only.
446             // For example, "/a/b" and "/a/b/" both imply "/a/b/"
447             if ((!that.location.getFile().equals(location.getFile()))
448                 && (!that.location.getFile().equals(location.getFile()+"/"))) {
449                 return false;
450             }
451         }
452
453         if (location.getRef() != null
454             && !location.getRef().equals(that.location.getRef())) {
455             return false;
456         }
457
458         String thisHost = location.getHost();
459         String thatHost = that.location.getHost();
460         if (thisHost != null) {
461             if (("".equals(thisHost) || "localhost".equals(thisHost)) &&
462                 ("".equals(thatHost) || "localhost".equals(thatHost))) {
463                 // ok
464             } else if (!thisHost.equals(thatHost)) {
465                 if (thatHost == null) {
466                     return false;
467                 }
468                 if (this.sp == null) {
469                     this.sp = new SocketPermission(thisHost, "resolve");
470                 }
471                 if (that.sp == null) {
472                     that.sp = new SocketPermission(thatHost, "resolve");
473                 }
474                 if (!this.sp.implies(that.sp)) {
475                     return false;
476                 }
477             }
478         }
479         // everything matches
480         return true;
481     }
482
483     /**
484      * Returns a string describing this CodeSource, telling its
485      * URL and certificates.
486      *
487      * @return information about this CodeSource.
488      */

489     @Override
490     public String toString() {
491         StringBuilder sb = new StringBuilder();
492         sb.append("(");
493         sb.append(this.location);
494
495         if (this.certs != null && this.certs.length > 0) {
496             for (int i = 0; i < this.certs.length; i++) {
497                 sb.append( " " + this.certs[i]);
498             }
499
500         } else if (this.signers != null && this.signers.length > 0) {
501             for (int i = 0; i < this.signers.length; i++) {
502                 sb.append( " " + this.signers[i]);
503             }
504         } else {
505             sb.append(" <no signer certificates>");
506         }
507         sb.append(")");
508         return sb.toString();
509     }
510
511     /**
512      * Writes this object out to a stream (i.e., serializes it).
513      *
514      * @serialData An initial {@code URL} is followed by an
515      * {@code int} indicating the number of certificates to follow
516      * (a value of "zero" denotes that there are no certificates associated
517      * with this object).
518      * Each certificate is written out starting with a {@code String}
519      * denoting the certificate type, followed by an
520      * {@code int} specifying the length of the certificate encoding,
521      * followed by the certificate encoding itself which is written out as an
522      * array of bytes. Finally, if any code signers are present then the array
523      * of code signers is serialized and written out too.
524      */

525     private void writeObject(java.io.ObjectOutputStream oos)
526         throws IOException
527     {
528         oos.defaultWriteObject(); // location
529
530         // Serialize the array of certs
531         if (certs == null || certs.length == 0) {
532             oos.writeInt(0);
533         } else {
534             // write out the total number of certs
535             oos.writeInt(certs.length);
536             // write out each cert, including its type
537             for (int i = 0; i < certs.length; i++) {
538                 java.security.cert.Certificate cert = certs[i];
539                 try {
540                     oos.writeUTF(cert.getType());
541                     byte[] encoded = cert.getEncoded();
542                     oos.writeInt(encoded.length);
543                     oos.write(encoded);
544                 } catch (CertificateEncodingException cee) {
545                     throw new IOException(cee.getMessage());
546                 }
547             }
548         }
549
550         // Serialize the array of code signers (if any)
551         if (signers != null && signers.length > 0) {
552             oos.writeObject(signers);
553         }
554     }
555
556     /**
557      * Restores this object from a stream (i.e., deserializes it).
558      */

559     private void readObject(java.io.ObjectInputStream ois)
560         throws IOException, ClassNotFoundException
561     {
562         CertificateFactory cf;
563         Hashtable<String, CertificateFactory> cfs = null;
564         List<java.security.cert.Certificate> certList = null;
565
566         ois.defaultReadObject(); // location
567
568         // process any new-style certs in the stream (if present)
569         int size = ois.readInt();
570         if (size > 0) {
571             // we know of 3 different cert types: X.509, PGP, SDSI, which
572             // could all be present in the stream at the same time
573             cfs = new Hashtable<>(3);
574             certList = new ArrayList<>(size > 20 ? 20 : size);
575         } else if (size < 0) {
576             throw new IOException("size cannot be negative");
577         }
578
579         for (int i = 0; i < size; i++) {
580             // read the certificate type, and instantiate a certificate
581             // factory of that type (reuse existing factory if possible)
582             String certType = ois.readUTF();
583             if (cfs.containsKey(certType)) {
584                 // reuse certificate factory
585                 cf = cfs.get(certType);
586             } else {
587                 // create new certificate factory
588                 try {
589                     cf = CertificateFactory.getInstance(certType);
590                 } catch (CertificateException ce) {
591                     throw new ClassNotFoundException
592                         ("Certificate factory for " + certType + " not found");
593                 }
594                 // store the certificate factory so we can reuse it later
595                 cfs.put(certType, cf);
596             }
597             // parse the certificate
598             byte[] encoded = IOUtils.readExactlyNBytes(ois, ois.readInt());
599             ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
600             try {
601                 certList.add(cf.generateCertificate(bais));
602             } catch (CertificateException ce) {
603                 throw new IOException(ce.getMessage());
604             }
605             bais.close();
606         }
607
608         if (certList != null) {
609             this.certs = certList.toArray(
610                     new java.security.cert.Certificate[size]);
611         }
612         // Deserialize array of code signers (if any)
613         try {
614             this.signers = ((CodeSigner[])ois.readObject()).clone();
615         } catch (IOException ioe) {
616             // no signers present
617         }
618
619         if (location != null) {
620             locationNoFragString = URLUtil.urlNoFragString(location);
621         }
622     }
623
624     /*
625      * Convert an array of certificates to an array of code signers.
626      * The array of certificates is a concatenation of certificate chains
627      * where the initial certificate in each chain is the end-entity cert.
628      *
629      * @return an array of code signers or null if none are generated.
630      */

631     private CodeSigner[] convertCertArrayToSignerArray(
632         java.security.cert.Certificate[] certs) {
633
634         if (certs == null) {
635             return null;
636         }
637
638         try {
639             // Initialize certificate factory
640             if (factory == null) {
641                 factory = CertificateFactory.getInstance("X.509");
642             }
643
644             // Iterate through all the certificates
645             int i = 0;
646             List<CodeSigner> signers = new ArrayList<>();
647             while (i < certs.length) {
648                 List<java.security.cert.Certificate> certChain =
649                         new ArrayList<>();
650                 certChain.add(certs[i++]); // first cert is an end-entity cert
651                 int j = i;
652
653                 // Extract chain of certificates
654                 // (loop while certs are not end-entity certs)
655                 while (j < certs.length &&
656                     certs[j] instanceof X509Certificate &&
657                     ((X509Certificate)certs[j]).getBasicConstraints() != -1) {
658                     certChain.add(certs[j]);
659                     j++;
660                 }
661                 i = j;
662                 CertPath certPath = factory.generateCertPath(certChain);
663                 signers.add(new CodeSigner(certPath, null));
664             }
665
666             if (signers.isEmpty()) {
667                 return null;
668             } else {
669                 return signers.toArray(new CodeSigner[signers.size()]);
670             }
671
672         } catch (CertificateException e) {
673             return null//TODO - may be better to throw an ex. here
674         }
675     }
676 }
677