/* * @(#)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.*; /** * *
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. *
* 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. *
* 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. *
* More specifically, this method makes the following checks, in order. * If any fail, it returns false. If they all succeed, it returns true.
*
*
* 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: *
* http: * http://*.sun.com/classes/* * http://java.sun.com/classes/- * http://java.sun.com/classes/foo.jar ** * 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 that. * * @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("
URL
is followed by an
* int
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 String
* denoting the certificate type, followed by an
* int
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
}
}
}