Path: blob/master/src/java.base/share/classes/java/security/CodeSource.java
41152 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.security;262728import java.net.URL;29import java.net.SocketPermission;30import java.util.ArrayList;31import java.util.List;32import java.util.Hashtable;33import java.io.ByteArrayInputStream;34import java.io.IOException;35import java.security.cert.*;36import java.util.Objects;3738import sun.net.util.URLUtil;39import sun.security.util.IOUtils;4041/**42*43* <p>This class extends the concept of a codebase to44* encapsulate not only the location (URL) but also the certificate chains45* that were used to verify signed code originating from that location.46*47* @author Li Gong48* @author Roland Schemers49* @since 1.250*/5152public class CodeSource implements java.io.Serializable {5354@java.io.Serial55private static final long serialVersionUID = 4977541819976013951L;5657/**58* The code location.59*60* @serial61*/62private final URL location;6364/*65* The code signers.66*/67private transient CodeSigner[] signers = null;6869/*70* The code signers. Certificate chains are concatenated.71*/72private transient java.security.cert.Certificate[] certs = null;7374// cached SocketPermission used for matchLocation75private transient SocketPermission sp;7677// for generating cert paths78private transient CertificateFactory factory = null;7980/**81* A String form of the URL for use as a key in HashMaps/Sets. The String82* form should be behave in the same manner as the URL when compared for83* equality in a HashMap/Set, except that no nameservice lookup is done84* on the hostname (only string comparison), and the fragment is not85* considered.86*/87private transient String locationNoFragString;8889/**90* Constructs a CodeSource and associates it with the specified91* location and set of certificates.92*93* @param url the location (URL). It may be {@code null}.94* @param certs the certificate(s). It may be {@code null}. The contents95* of the array are copied to protect against subsequent modification.96*/97public CodeSource(URL url, java.security.cert.Certificate[] certs) {98this.location = url;99if (url != null) {100this.locationNoFragString = URLUtil.urlNoFragString(url);101}102103// Copy the supplied certs104if (certs != null) {105this.certs = certs.clone();106}107}108109/**110* Constructs a CodeSource and associates it with the specified111* location and set of code signers.112*113* @param url the location (URL). It may be {@code null}.114* @param signers the code signers. It may be {@code null}. The contents115* of the array are copied to protect against subsequent modification.116*117* @since 1.5118*/119public CodeSource(URL url, CodeSigner[] signers) {120this.location = url;121if (url != null) {122this.locationNoFragString = URLUtil.urlNoFragString(url);123}124125// Copy the supplied signers126if (signers != null) {127this.signers = signers.clone();128}129}130131/**132* Returns the hash code value for this object.133*134* @return a hash code value for this object.135*/136@Override137public int hashCode() {138if (location != null)139return location.hashCode();140else141return 0;142}143144/**145* Tests for equality between the specified object and this146* object. Two CodeSource objects are considered equal if their147* locations are of identical value and if their signer certificate148* chains are of identical value. It is not required that149* the certificate chains be in the same order.150*151* @param obj the object to test for equality with this object.152*153* @return true if the objects are considered equal, false otherwise.154*/155@Override156public boolean equals(Object obj) {157if (obj == this)158return true;159160// objects types must be equal161return (obj instanceof CodeSource other)162&& Objects.equals(location, other.location)163&& matchCerts(other, true);164}165166/**167* Returns the location associated with this CodeSource.168*169* @return the location (URL), or {@code null} if no URL was supplied170* during construction.171*/172public final URL getLocation() {173/* since URL is practically immutable, returning itself is not174a security problem */175return this.location;176}177178/**179* Returns a String form of the URL for use as a key in HashMaps/Sets.180*/181String getLocationNoFragString() {182return locationNoFragString;183}184185/**186* Returns the certificates associated with this CodeSource.187* <p>188* If this CodeSource object was created using the189* {@link #CodeSource(URL url, CodeSigner[] signers)}190* constructor then its certificate chains are extracted and used to191* create an array of Certificate objects. Each signer certificate is192* followed by its supporting certificate chain (which may be empty).193* Each signer certificate and its supporting certificate chain is ordered194* bottom-to-top (i.e., with the signer certificate first and the (root)195* certificate authority last).196*197* @return a copy of the certificate array, or {@code null} if there198* is none.199*/200public final java.security.cert.Certificate[] getCertificates() {201if (certs != null) {202return certs.clone();203204} else if (signers != null) {205// Convert the code signers to certs206ArrayList<java.security.cert.Certificate> certChains =207new ArrayList<>();208for (int i = 0; i < signers.length; i++) {209certChains.addAll(210signers[i].getSignerCertPath().getCertificates());211}212certs = certChains.toArray(213new java.security.cert.Certificate[certChains.size()]);214return certs.clone();215216} else {217return null;218}219}220221/**222* Returns the code signers associated with this CodeSource.223* <p>224* If this CodeSource object was created using the225* {@link #CodeSource(URL url, java.security.cert.Certificate[] certs)}226* constructor then its certificate chains are extracted and used to227* create an array of CodeSigner objects. Note that only X.509 certificates228* are examined - all other certificate types are ignored.229*230* @return a copy of the code signer array, or {@code null} if there231* is none.232*233* @since 1.5234*/235public final CodeSigner[] getCodeSigners() {236if (signers != null) {237return signers.clone();238239} else if (certs != null) {240// Convert the certs to code signers241signers = convertCertArrayToSignerArray(certs);242return signers.clone();243244} else {245return null;246}247}248249/**250* Returns true if this CodeSource object "implies" the specified CodeSource.251* <p>252* More specifically, this method makes the following checks.253* If any fail, it returns false. If they all succeed, it returns true.254* <ul>255* <li> <i>codesource</i> must not be null.256* <li> If this object's certificates are not null, then all257* of this object's certificates must be present in <i>codesource</i>'s258* certificates.259* <li> If this object's location (getLocation()) is not null, then the260* following checks are made against this object's location and261* <i>codesource</i>'s:262* <ul>263* <li> <i>codesource</i>'s location must not be null.264*265* <li> If this object's location266* equals <i>codesource</i>'s location, then return true.267*268* <li> This object's protocol (getLocation().getProtocol()) must be269* equal to <i>codesource</i>'s protocol, ignoring case.270*271* <li> If this object's host (getLocation().getHost()) is not null,272* then the SocketPermission273* constructed with this object's host must imply the274* SocketPermission constructed with <i>codesource</i>'s host.275*276* <li> If this object's port (getLocation().getPort()) is not277* equal to -1 (that is, if a port is specified), it must equal278* <i>codesource</i>'s port or default port279* (codesource.getLocation().getDefaultPort()).280*281* <li> If this object's file (getLocation().getFile()) doesn't equal282* <i>codesource</i>'s file, then the following checks are made:283* If this object's file ends with "/-",284* then <i>codesource</i>'s file must start with this object's285* file (exclusive the trailing "-").286* If this object's file ends with a "/*",287* then <i>codesource</i>'s file must start with this object's288* file and must not have any further "/" separators.289* If this object's file doesn't end with a "/",290* then <i>codesource</i>'s file must match this object's291* file with a '/' appended.292*293* <li> If this object's reference (getLocation().getRef()) is294* not null, it must equal <i>codesource</i>'s reference.295*296* </ul>297* </ul>298* <p>299* For example, the codesource objects with the following locations300* and null certificates all imply301* the codesource with the location "http://www.example.com/classes/foo.jar"302* and null certificates:303* <pre>304* http:305* http://*.example.com/classes/*306* http://www.example.com/classes/-307* http://www.example.com/classes/foo.jar308* </pre>309*310* Note that if this CodeSource has a null location and a null311* certificate chain, then it implies every other CodeSource.312*313* @param codesource CodeSource to compare against.314*315* @return true if the specified codesource is implied by this codesource,316* false if not.317*/318public boolean implies(CodeSource codesource)319{320if (codesource == null)321return false;322323return matchCerts(codesource, false) && matchLocation(codesource);324}325326/**327* Returns true if all the certs in this328* CodeSource are also in <i>that</i>.329*330* @param that the CodeSource to check against.331* @param strict if true then a strict equality match is performed.332* Otherwise a subset match is performed.333*/334boolean matchCerts(CodeSource that, boolean strict)335{336boolean match;337338// match any key339if (certs == null && signers == null) {340if (strict) {341return (that.certs == null && that.signers == null);342} else {343return true;344}345// both have signers346} else if (signers != null && that.signers != null) {347if (strict && signers.length != that.signers.length) {348return false;349}350for (int i = 0; i < signers.length; i++) {351match = false;352for (int j = 0; j < that.signers.length; j++) {353if (signers[i].equals(that.signers[j])) {354match = true;355break;356}357}358if (!match) return false;359}360return true;361362// both have certs363} else if (certs != null && that.certs != null) {364if (strict && certs.length != that.certs.length) {365return false;366}367for (int i = 0; i < certs.length; i++) {368match = false;369for (int j = 0; j < that.certs.length; j++) {370if (certs[i].equals(that.certs[j])) {371match = true;372break;373}374}375if (!match) return false;376}377return true;378}379380return false;381}382383384/**385* Returns true if two CodeSource's have the "same" location.386*387* @param that CodeSource to compare against388*/389private boolean matchLocation(CodeSource that) {390if (location == null)391return true;392393if ((that == null) || (that.location == null))394return false;395396if (location.equals(that.location))397return true;398399if (!location.getProtocol().equalsIgnoreCase(that.location.getProtocol()))400return false;401402int thisPort = location.getPort();403if (thisPort != -1) {404int thatPort = that.location.getPort();405int port = thatPort != -1 ? thatPort406: that.location.getDefaultPort();407if (thisPort != port)408return false;409}410411if (location.getFile().endsWith("/-")) {412// Matches the directory and (recursively) all files413// and subdirectories contained in that directory.414// For example, "/a/b/-" implies anything that starts with415// "/a/b/"416String thisPath = location.getFile().substring(0,417location.getFile().length()-1);418if (!that.location.getFile().startsWith(thisPath))419return false;420} else if (location.getFile().endsWith("/*")) {421// Matches the directory and all the files contained in that422// directory.423// For example, "/a/b/*" implies anything that starts with424// "/a/b/" but has no further slashes425int last = that.location.getFile().lastIndexOf('/');426if (last == -1)427return false;428String thisPath = location.getFile().substring(0,429location.getFile().length()-1);430String thatPath = that.location.getFile().substring(0, last+1);431if (!thatPath.equals(thisPath))432return false;433} else {434// Exact matches only.435// For example, "/a/b" and "/a/b/" both imply "/a/b/"436if ((!that.location.getFile().equals(location.getFile()))437&& (!that.location.getFile().equals(location.getFile()+"/"))) {438return false;439}440}441442if (location.getRef() != null443&& !location.getRef().equals(that.location.getRef())) {444return false;445}446447String thisHost = location.getHost();448String thatHost = that.location.getHost();449if (thisHost != null) {450if (("".equals(thisHost) || "localhost".equals(thisHost)) &&451("".equals(thatHost) || "localhost".equals(thatHost))) {452// ok453} else if (!thisHost.equals(thatHost)) {454if (thatHost == null) {455return false;456}457if (this.sp == null) {458this.sp = new SocketPermission(thisHost, "resolve");459}460if (that.sp == null) {461that.sp = new SocketPermission(thatHost, "resolve");462}463if (!this.sp.implies(that.sp)) {464return false;465}466}467}468// everything matches469return true;470}471472/**473* Returns a string describing this CodeSource, telling its474* URL and certificates.475*476* @return information about this CodeSource.477*/478@Override479public String toString() {480StringBuilder sb = new StringBuilder();481sb.append("(");482sb.append(this.location);483484if (this.certs != null && this.certs.length > 0) {485for (int i = 0; i < this.certs.length; i++) {486sb.append( " " + this.certs[i]);487}488489} else if (this.signers != null && this.signers.length > 0) {490for (int i = 0; i < this.signers.length; i++) {491sb.append( " " + this.signers[i]);492}493} else {494sb.append(" <no signer certificates>");495}496sb.append(")");497return sb.toString();498}499500/**501* Writes this object out to a stream (i.e., serializes it).502*503* @serialData An initial {@code URL} is followed by an504* {@code int} indicating the number of certificates to follow505* (a value of "zero" denotes that there are no certificates associated506* with this object).507* Each certificate is written out starting with a {@code String}508* denoting the certificate type, followed by an509* {@code int} specifying the length of the certificate encoding,510* followed by the certificate encoding itself which is written out as an511* array of bytes. Finally, if any code signers are present then the array512* of code signers is serialized and written out too.513*514* @param oos the {@code ObjectOutputStream} to which data is written515* @throws IOException if an I/O error occurs516*/517@java.io.Serial518private void writeObject(java.io.ObjectOutputStream oos)519throws IOException520{521oos.defaultWriteObject(); // location522523// Serialize the array of certs524if (certs == null || certs.length == 0) {525oos.writeInt(0);526} else {527// write out the total number of certs528oos.writeInt(certs.length);529// write out each cert, including its type530for (int i = 0; i < certs.length; i++) {531java.security.cert.Certificate cert = certs[i];532try {533oos.writeUTF(cert.getType());534byte[] encoded = cert.getEncoded();535oos.writeInt(encoded.length);536oos.write(encoded);537} catch (CertificateEncodingException cee) {538throw new IOException(cee.getMessage());539}540}541}542543// Serialize the array of code signers (if any)544if (signers != null && signers.length > 0) {545oos.writeObject(signers);546}547}548549/**550* Restores this object from a stream (i.e., deserializes it).551*552* @param ois the {@code ObjectInputStream} from which data is read553* @throws IOException if an I/O error occurs554* @throws ClassNotFoundException if a serialized class cannot be loaded555*/556@java.io.Serial557private void readObject(java.io.ObjectInputStream ois)558throws IOException, ClassNotFoundException559{560CertificateFactory cf;561Hashtable<String, CertificateFactory> cfs = null;562List<java.security.cert.Certificate> certList = null;563564ois.defaultReadObject(); // location565566// process any new-style certs in the stream (if present)567int size = ois.readInt();568if (size > 0) {569// we know of 3 different cert types: X.509, PGP, SDSI, which570// could all be present in the stream at the same time571cfs = new Hashtable<>(3);572certList = new ArrayList<>(size > 20 ? 20 : size);573} else if (size < 0) {574throw new IOException("size cannot be negative");575}576577for (int i = 0; i < size; i++) {578// read the certificate type, and instantiate a certificate579// factory of that type (reuse existing factory if possible)580String certType = ois.readUTF();581if (cfs.containsKey(certType)) {582// reuse certificate factory583cf = cfs.get(certType);584} else {585// create new certificate factory586try {587cf = CertificateFactory.getInstance(certType);588} catch (CertificateException ce) {589throw new ClassNotFoundException590("Certificate factory for " + certType + " not found");591}592// store the certificate factory so we can reuse it later593cfs.put(certType, cf);594}595// parse the certificate596byte[] encoded = IOUtils.readExactlyNBytes(ois, ois.readInt());597ByteArrayInputStream bais = new ByteArrayInputStream(encoded);598try {599certList.add(cf.generateCertificate(bais));600} catch (CertificateException ce) {601throw new IOException(ce.getMessage());602}603bais.close();604}605606if (certList != null) {607this.certs = certList.toArray(608new java.security.cert.Certificate[size]);609}610// Deserialize array of code signers (if any)611try {612this.signers = ((CodeSigner[])ois.readObject()).clone();613} catch (IOException ioe) {614// no signers present615}616617if (location != null) {618locationNoFragString = URLUtil.urlNoFragString(location);619}620}621622/*623* Convert an array of certificates to an array of code signers.624* The array of certificates is a concatenation of certificate chains625* where the initial certificate in each chain is the end-entity cert.626*627* @return an array of code signers or null if none are generated.628*/629private CodeSigner[] convertCertArrayToSignerArray(630java.security.cert.Certificate[] certs) {631632if (certs == null) {633return null;634}635636try {637// Initialize certificate factory638if (factory == null) {639factory = CertificateFactory.getInstance("X.509");640}641642// Iterate through all the certificates643int i = 0;644List<CodeSigner> signers = new ArrayList<>();645while (i < certs.length) {646List<java.security.cert.Certificate> certChain =647new ArrayList<>();648certChain.add(certs[i++]); // first cert is an end-entity cert649int j = i;650651// Extract chain of certificates652// (loop while certs are not end-entity certs)653while (j < certs.length &&654certs[j] instanceof X509Certificate &&655((X509Certificate)certs[j]).getBasicConstraints() != -1) {656certChain.add(certs[j]);657j++;658}659i = j;660CertPath certPath = factory.generateCertPath(certChain);661signers.add(new CodeSigner(certPath, null));662}663664if (signers.isEmpty()) {665return null;666} else {667return signers.toArray(new CodeSigner[signers.size()]);668}669670} catch (CertificateException e) {671return null; //TODO - may be better to throw an ex. here672}673}674}675676677