- /*
- * @(#)CodeSource.java 1.38 03/12/19
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
- package java.security;
-
-
- import java.net.URL;
- import java.net.SocketPermission;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Hashtable;
- import java.io.ByteArrayInputStream;
- import java.io.IOException;
- import java.security.cert.*;
-
- /**
- *
- * <p>This class extends the concept of a codebase to
- * encapsulate not only the location (URL) but also the certificate chains
- * that were used to verify signed code originating from that location.
- *
- * @version 1.38, 12/19/03
- * @author Li Gong
- * @author Roland Schemers
- */
-
- public class CodeSource implements java.io.Serializable {
-
- private static final long serialVersionUID = 4977541819976013951L;
-
- /**
- * The code location.
- *
- * @serial
- */
- private URL location;
-
- /*
- * The code signers.
- */
- private transient CodeSigner[] signers = null;
-
- /*
- * The code signers. Certificate chains are concatenated.
- */
- private transient java.security.cert.Certificate certs[] = null;
-
- // cached SocketPermission used for matchLocation
- private transient SocketPermission sp;
-
- // for generating cert paths
- private transient CertificateFactory factory = null;
-
- /**
- * Constructs a CodeSource and associates it with the specified
- * location and set of certificates.
- *
- * @param url the location (URL).
- *
- * @param certs the certificate(s). It may be null. The contents of the
- * array are copied to protect against subsequent modification.
- */
- public CodeSource(URL url, java.security.cert.Certificate certs[]) {
- this.location = url;
-
- // Copy the supplied certs
- if (certs != null) {
- this.certs = (java.security.cert.Certificate[]) certs.clone();
- }
- }
-
- /**
- * Constructs a CodeSource and associates it with the specified
- * location and set of code signers.
- *
- * @param url the location (URL).
- * @param signers the code signers. It may be null. The contents of the
- * array are copied to protect against subsequent modification.
- *
- * @since 1.5
- */
- public CodeSource(URL url, CodeSigner[] signers) {
- this.location = url;
-
- // Copy the supplied signers
- if (signers != null) {
- this.signers = (CodeSigner[])signers.clone();
- }
- }
-
- /**
- * Returns the hash code value for this object.
- *
- * @return a hash code value for this object.
- */
-
- public int hashCode() {
- if (location != null)
- return location.hashCode();
- else
- return 0;
- }
-
- /**
- * Tests for equality between the specified object and this
- * object. Two CodeSource objects are considered equal if their
- * locations are of identical value and if their signer certificate
- * chains are of identical value. It is not required that
- * the certificate chains be in the same order.
- *
- * @param obj the object to test for equality with this object.
- *
- * @return true if the objects are considered equal, false otherwise.
- */
- public boolean equals(Object obj) {
- if (obj == this)
- return true;
-
- // objects types must be equal
- if (!(obj instanceof CodeSource))
- return false;
-
- CodeSource cs = (CodeSource) obj;
-
- // URLs must match
- if (location == null) {
- // if location is null, then cs.location must be null as well
- if (cs.location != null) return false;
- } else {
- // if location is not null, then it must equal cs.location
- if (!location.equals(cs.location)) return false;
- }
-
- // certs must match
- return matchCerts(cs, true);
- }
-
- /**
- * Returns the location associated with this CodeSource.
- *
- * @return the location (URL).
- */
- public final URL getLocation() {
- /* since URL is practically immutable, returning itself is not
- a security problem */
- return this.location;
- }
-
- /**
- * Returns the certificates associated with this CodeSource.
- * <p>
- * If this CodeSource object was created using the
- * {@link #CodeSource(URL url, CodeSigner[] signers)}
- * constructor then its certificate chains are extracted and used to
- * create an array of Certificate objects. Each signer certificate is
- * followed by its supporting certificate chain (which may be empty).
- * Each signer certificate and its supporting certificate chain is ordered
- * bottom-to-top (i.e., with the signer certificate first and the (root)
- * certificate authority last).
- *
- * @return A copy of the certificates array, or null if there is none.
- */
- public final java.security.cert.Certificate[] getCertificates() {
- if (certs != null) {
- return (java.security.cert.Certificate[]) certs.clone();
-
- } else if (signers != null) {
- // Convert the code signers to certs
- ArrayList certChains = new ArrayList();
- for (int i = 0; i < signers.length; i++) {
- certChains.addAll(
- signers[i].getSignerCertPath().getCertificates());
- }
- certs = (java.security.cert.Certificate[])
- certChains.toArray(
- new java.security.cert.Certificate[certChains.size()]);
- return (java.security.cert.Certificate[]) certs.clone();
-
- } else {
- return null;
- }
- }
-
- /**
- * Returns the code signers associated with this CodeSource.
- * <p>
- * If this CodeSource object was created using the
- * {@link #CodeSource(URL url, Certificate[] certs)}
- * constructor then its certificate chains are extracted and used to
- * create an array of CodeSigner objects. Note that only X.509 certificates
- * are examined - all other certificate types are ignored.
- *
- * @return A copy of the code signer array, or null if there is none.
- *
- * @since 1.5
- */
- public final CodeSigner[] getCodeSigners() {
- if (signers != null) {
- return (CodeSigner[]) signers.clone();
-
- } else if (certs != null) {
- // Convert the certs to code signers
- signers = convertCertArrayToSignerArray(certs);
- return (CodeSigner[]) signers.clone();
-
- } else {
- return null;
- }
- }
-
- /**
- * Returns true if this CodeSource object "implies" the specified CodeSource.
- * <P>
- * More specifically, this method makes the following checks, in order.
- * If any fail, it returns false. If they all succeed, it returns true.<p>
- * <ol>
- * <li> <i>codesource</i> must not be null.
- * <li> If this object's certificates are not null, then all
- * of this object's certificates must be present in <i>codesource</i>'s
- * certificates.
- * <li> If this object's location (getLocation()) is not null, then the
- * following checks are made against this object's location and
- * <i>codesource</i>'s:<p>
- * <ol>
- * <li> <i>codesource</i>'s location must not be null.
- *
- * <li> If this object's location
- * equals <i>codesource</i>'s location, then return true.
- *
- * <li> This object's protocol (getLocation().getProtocol()) must be
- * equal to <i>codesource</i>'s protocol.
- *
- * <li> If this object's host (getLocation().getHost()) is not null,
- * then the SocketPermission
- * constructed with this object's host must imply the
- * SocketPermission constructed with <i>codesource</i>'s host.
- *
- * <li> If this object's port (getLocation().getPort()) is not
- * equal to -1 (that is, if a port is specified), it must equal
- * <i>codesource</i>'s port.
- *
- * <li> If this object's file (getLocation().getFile()) doesn't equal
- * <i>codesource</i>'s file, then the following checks are made:
- * If this object's file ends with "/-",
- * then <i>codesource</i>'s file must start with this object's
- * file (exclusive the trailing "-").
- * If this object's file ends with a "/*",
- * then <i>codesource</i>'s file must start with this object's
- * file and must not have any further "/" separators.
- * If this object's file doesn't end with a "/",
- * then <i>codesource</i>'s file must match this object's
- * file with a '/' appended.
- *
- * <li> If this object's reference (getLocation().getRef()) is
- * not null, it must equal <i>codesource</i>'s reference.
- *
- * </ol>
- * </ol>
- * <p>
- * For example, the codesource objects with the following locations
- * and null certificates all imply
- * the codesource with the location "http://java.sun.com/classes/foo.jar"
- * and null certificates:
- * <pre>
- * http:
- * http://*.sun.com/classes/*
- * http://java.sun.com/classes/-
- * http://java.sun.com/classes/foo.jar
- * </pre>
- *
- * Note that if this CodeSource has a null location and a null
- * certificate chain, then it implies every other CodeSource.
- *
- * @param codesource CodeSource to compare against.
- *
- * @return true if the specified codesource is implied by this codesource,
- * false if not.
- */
-
- public boolean implies(CodeSource codesource)
- {
- if (codesource == null)
- return false;
-
- return matchCerts(codesource, false) && matchLocation(codesource);
- }
-
- /**
- * Returns true if all the certs in this
- * CodeSource are also in <i>that</i>.
- *
- * @param that the CodeSource to check against.
- * @param strict If true then a strict equality match is performed.
- * Otherwise a subset match is performed.
- */
- private boolean matchCerts(CodeSource that, boolean strict)
- {
- // match any key
- if (certs == null && signers == null)
- return true;
-
- // match no key
- if (that.certs == null && that.signers == null)
- return false;
-
- boolean match;
- // both have signers
- if (signers != null && that.signers != null) {
- if (strict && signers.length != that.signers.length) {
- return false;
- }
- for (int i = 0; i < signers.length; i++) {
- match = false;
- for (int j = 0; j < that.signers.length; j++) {
- if (signers[i].equals(that.signers[j])) {
- match = true;
- break;
- }
- }
- if (!match) return false;
- }
- return true;
-
- // both have certs
- } else {
- if (strict && certs.length != that.certs.length) {
- return false;
- }
- for (int i = 0; i < certs.length; i++) {
- match = false;
- for (int j = 0; j < that.certs.length; j++) {
- if (certs[i].equals(that.certs[j])) {
- match = true;
- break;
- }
- }
- if (!match) return false;
- }
- return true;
- }
- }
-
-
- /**
- * Returns true if two CodeSource's have the "same" location.
- *
- * @param that CodeSource to compare against
- */
- private boolean matchLocation(CodeSource that)
- {
- if (location == null) {
- return true;
- }
-
- if ((that == null) || (that.location == null))
- return false;
-
- if (location.equals(that.location))
- return true;
-
- if (!location.getProtocol().equals(that.location.getProtocol()))
- return false;
-
- String thisHost = location.getHost();
- String thatHost = that.location.getHost();
-
- if (thisHost != null) {
- if (("".equals(thisHost) || "localhost".equals(thisHost)) &&
- ("".equals(thatHost) || "localhost".equals(thatHost))) {
- // ok
- } else if (!thisHost.equals(thatHost)) {
- if (thatHost == null) {
- return false;
- }
- if (this.sp == null) {
- this.sp = new SocketPermission(thisHost, "resolve");
- }
- if (that.sp == null) {
- that.sp = new SocketPermission(thatHost, "resolve");
- }
- if (!this.sp.implies(that.sp)) {
- return false;
- }
- }
- }
-
- if (location.getPort() != -1) {
- if (location.getPort() != that.location.getPort())
- return false;
- }
-
- if (location.getFile().endsWith("/-")) {
- // Matches the directory and (recursively) all files
- // and subdirectories contained in that directory.
- // For example, "/a/b/-" implies anything that starts with
- // "/a/b/"
- String thisPath = location.getFile().substring(0,
- location.getFile().length()-1);
- if (!that.location.getFile().startsWith(thisPath))
- return false;
- } else if (location.getFile().endsWith("/*")) {
- // Matches the directory and all the files contained in that
- // directory.
- // For example, "/a/b/*" implies anything that starts with
- // "/a/b/" but has no further slashes
- int last = that.location.getFile().lastIndexOf('/');
- if (last == -1)
- return false;
- String thisPath = location.getFile().substring(0,
- location.getFile().length()-1);
- String thatPath = that.location.getFile().substring(0, last+1);
- if (!thatPath.equals(thisPath))
- return false;
- } else {
- // Exact matches only.
- // For example, "/a/b" and "/a/b/" both imply "/a/b/"
- if ((!that.location.getFile().equals(location.getFile()))
- && (!that.location.getFile().equals(location.getFile()+"/"))) {
- return false;
- }
- }
-
- if (location.getRef() == null)
- return true;
- else
- return location.getRef().equals(that.location.getRef());
- }
-
- /**
- * Returns a string describing this CodeSource, telling its
- * URL and certificates.
- *
- * @return information about this CodeSource.
- */
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("(");
- sb.append(this.location);
-
- if (this.certs != null && this.certs.length > 0) {
- for (int i = 0; i < this.certs.length; i++) {
- sb.append( " " + this.certs[i]);
- }
-
- } else if (this.signers != null && this.signers.length > 0) {
- for (int i = 0; i < this.signers.length; i++) {
- sb.append( " " + this.signers[i]);
- }
- } else {
- sb.append(" <no signer certificates>");
- }
- sb.append(")");
- return sb.toString();
- }
-
- /**
- * Writes this object out to a stream (i.e., serializes it).
- *
- * @serialData An initial <code>URL</code> is followed by an
- * <code>int</code> indicating the number of certificates to follow
- * (a value of "zero" denotes that there are no certificates associated
- * with this object).
- * Each certificate is written out starting with a <code>String</code>
- * denoting the certificate type, followed by an
- * <code>int</code> specifying the length of the certificate encoding,
- * followed by the certificate encoding itself which is written out as an
- * array of bytes. Finally, if any code signers are present then the array
- * of code signers is serialized and written out too.
- */
- private synchronized void writeObject(java.io.ObjectOutputStream oos)
- throws IOException
- {
- oos.defaultWriteObject(); // location
-
- // Serialize the array of certs
- if (certs == null || certs.length == 0) {
- oos.writeInt(0);
- } else {
- // write out the total number of certs
- oos.writeInt(certs.length);
- // write out each cert, including its type
- for (int i = 0; i < certs.length; i++) {
- java.security.cert.Certificate cert = certs[i];
- try {
- oos.writeUTF(cert.getType());
- byte[] encoded = cert.getEncoded();
- oos.writeInt(encoded.length);
- oos.write(encoded);
- } catch (CertificateEncodingException cee) {
- throw new IOException(cee.getMessage());
- }
- }
- }
-
- // Serialize the array of code signers (if any)
- if (signers != null && signers.length > 0) {
- oos.writeObject(signers);
- }
- }
-
- /**
- * Restores this object from a stream (i.e., deserializes it).
- */
- private synchronized void readObject(java.io.ObjectInputStream ois)
- throws IOException, ClassNotFoundException
- {
- CertificateFactory cf;
- Hashtable cfs = null;
-
- ois.defaultReadObject(); // location
-
- // process any new-style certs in the stream (if present)
- int size = ois.readInt();
- if (size > 0) {
- // we know of 3 different cert types: X.509, PGP, SDSI, which
- // could all be present in the stream at the same time
- cfs = new Hashtable(3);
- this.certs = new java.security.cert.Certificate[size];
- }
-
- for (int i = 0; i < size; i++) {
- // read the certificate type, and instantiate a certificate
- // factory of that type (reuse existing factory if possible)
- String certType = ois.readUTF();
- if (cfs.containsKey(certType)) {
- // reuse certificate factory
- cf = (CertificateFactory)cfs.get(certType);
- } else {
- // create new certificate factory
- try {
- cf = CertificateFactory.getInstance(certType);
- } catch (CertificateException ce) {
- throw new ClassNotFoundException
- ("Certificate factory for " + certType + " not found");
- }
- // store the certificate factory so we can reuse it later
- cfs.put(certType, cf);
- }
- // parse the certificate
- byte[] encoded = null;
- try {
- encoded = new byte[ois.readInt()];
- } catch (OutOfMemoryError oome) {
- throw new IOException("Certificate too big");
- }
- ois.readFully(encoded);
- ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
- try {
- this.certs[i] = cf.generateCertificate(bais);
- } catch (CertificateException ce) {
- throw new IOException(ce.getMessage());
- }
- bais.close();
- }
-
- // Deserialize array of code signers (if any)
- try {
- this.signers = (CodeSigner[])ois.readObject();
- } catch (IOException ioe) {
- // no signers present
- }
- }
-
- /*
- * Convert an array of certificates to an array of code signers.
- * The array of certificates is a concatenation of certificate chains
- * where the initial certificate in each chain is the end-entity cert.
- *
- * @return An array of code signers or null if none are generated.
- */
- private CodeSigner[] convertCertArrayToSignerArray(
- java.security.cert.Certificate[] certs) {
-
- if (certs == null) {
- return null;
- }
-
- try {
- // Initialize certificate factory
- if (factory == null) {
- factory = CertificateFactory.getInstance("X.509");
- }
-
- // Iterate through all the certificates
- int i = 0;
- List signers = new ArrayList();
- while (i < certs.length) {
- List certChain = new ArrayList();
- certChain.add(certs[i++]); // first cert is an end-entity cert
- int j = i;
-
- // Extract chain of certificates
- // (loop while certs are not end-entity certs)
- while (j < certs.length &&
- certs[j] instanceof X509Certificate &&
- ((X509Certificate)certs[j]).getBasicConstraints() != -1) {
- certChain.add(certs[j]);
- j++;
- }
- i = j;
- CertPath certPath = factory.generateCertPath(certChain);
- signers.add(new CodeSigner(certPath, null));
- }
-
- if (signers.isEmpty()) {
- return null;
- } else {
- return (CodeSigner[])
- signers.toArray(new CodeSigner[signers.size()]);
- }
-
- } catch (CertificateException e) {
- return null; //TODO - may be better to throw an ex. here
- }
- }
- }
-