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 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 != null) return 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 null} if 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 null} if 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 null} if 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://www.example.com/classes/foo.jar"
 313      * and null certificates:
 314      * <pre>
 315      *     http:
 316      *     http://*.example.com/classes/*
 317      *     http://www.example.com/classes/-
 318      *     http://www.example.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.readNBytes(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 }