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