1. /*
  2. * @(#)CodeSource.java 1.32 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.security;
  8. import java.net.URL;
  9. import java.net.SocketPermission;
  10. import java.util.Hashtable;
  11. import java.io.ByteArrayInputStream;
  12. import java.io.IOException;
  13. import java.security.cert.*;
  14. /**
  15. *
  16. * <p>This class extends the concept of a codebase to
  17. * encapsulate not only the location (URL) but also the certificate(s)
  18. * that were used to verify signed code originating from that
  19. * location.
  20. *
  21. * @version 1.32, 01/23/03
  22. * @author Li Gong
  23. * @author Roland Schemers
  24. */
  25. public class CodeSource implements java.io.Serializable {
  26. /**
  27. * The code location.
  28. *
  29. * @serial
  30. */
  31. private URL location;
  32. // certificates
  33. private transient java.security.cert.Certificate certs[];
  34. // cached SocketPermission used for matchLocation
  35. private transient SocketPermission sp;
  36. /**
  37. * Constructs a CodeSource and associates it with the specified
  38. * location and set of certificates.
  39. *
  40. * @param url the location (URL).
  41. *
  42. * @param certs the certificate(s).
  43. */
  44. public CodeSource(URL url, java.security.cert.Certificate certs[]) {
  45. this.location = url;
  46. if (certs != null)
  47. this.certs = (java.security.cert.Certificate[]) certs.clone();
  48. }
  49. /**
  50. * Returns the hash code value for this object.
  51. *
  52. * @return a hash code value for this object.
  53. */
  54. public int hashCode() {
  55. if (location != null)
  56. return location.hashCode();
  57. else
  58. return 0;
  59. }
  60. /**
  61. * Tests for equality between the specified object and this
  62. * object. Two CodeSource objects are considered equal if their
  63. * locations are of identical value and if the two sets of
  64. * certificates are of identical values. It is not required that
  65. * the certificates be in the same order.
  66. *
  67. * @param obj the object to test for equality with this object.
  68. *
  69. * @return true if the objects are considered equal, false otherwise.
  70. */
  71. public boolean equals(Object obj) {
  72. if (obj == this)
  73. return true;
  74. // objects types must be equal
  75. if (!(obj instanceof CodeSource))
  76. return false;
  77. CodeSource cs = (CodeSource) obj;
  78. // URLs must match
  79. if (location == null) {
  80. // if location is null, then cs.location must be null as well
  81. if (cs.location != null) return false;
  82. } else {
  83. // if location is not null, then it must equal cs.location
  84. if (!location.equals(cs.location)) return false;
  85. }
  86. // certs must match
  87. if (certs == null) {
  88. // if certs is null, then cs.certs must be null as well
  89. if (cs.certs != null) return false;
  90. } else {
  91. // if certs is not null, then it must equal cs.certs
  92. // equality means that both arrays of certs are the same set
  93. // step 1 -- every cert in certs[] must match one in cs.certs[]
  94. if (cs.certs == null)
  95. return false;
  96. boolean match;
  97. for (int i = 0; i < certs.length; i++) {
  98. match = false;
  99. for (int j = 0; j < cs.certs.length; j++) {
  100. if (certs[i].equals(cs.certs[j])) {
  101. match = true;
  102. break;
  103. }
  104. }
  105. if (!match) return false;
  106. }
  107. // step 2 -- every key in cs.certs[] must match one in certs[]
  108. for (int i = 0; i < cs.certs.length; i++) {
  109. match = false;
  110. for (int j = 0; j < certs.length; j++) {
  111. if (cs.certs[i].equals(certs[j])) {
  112. match = true;
  113. break;
  114. }
  115. }
  116. if (!match) return false;
  117. }
  118. }
  119. // they must be equal if we got here...
  120. return true;
  121. }
  122. /**
  123. * Returns the location associated with this CodeSource.
  124. *
  125. * @return the location (URL).
  126. */
  127. public final URL getLocation() {
  128. /* since URL is practically immutable, returning itself is not
  129. a security problem */
  130. return this.location;
  131. }
  132. /**
  133. * Returns the certificates associated with this CodeSource.
  134. *
  135. * @return the certificates
  136. */
  137. public final java.security.cert.Certificate[] getCertificates() {
  138. /* return a clone copy, to avoid malicious modification to the
  139. original object */
  140. if (this.certs != null) {
  141. return (java.security.cert.Certificate[])this.certs.clone();
  142. } else {
  143. return null;
  144. }
  145. }
  146. /**
  147. * Returns true if this CodeSource object "implies" the specified CodeSource.
  148. * <P>
  149. * More specifically, this method makes the following checks, in order.
  150. * If any fail, it returns false. If they all succeed, it returns true.<p>
  151. * <ol>
  152. * <li> <i>codesource</i> must not be null.
  153. * <li> If this object's certificates are not null, then all
  154. * of this object's certificates must be present in <i>codesource</i>'s
  155. * certificates.
  156. * <li> If this object's location (getLocation()) is not null, then the
  157. * following checks are made against this object's location and
  158. * <i>codesource</i>'s:<p>
  159. * <ol>
  160. * <li> <i>codesource</i>'s location must not be null.
  161. *
  162. * <li> If this object's location
  163. * equals <i>codesource</i>'s location, then return true.
  164. *
  165. * <li> This object's protocol (getLocation().getProtocol()) must be
  166. * equal to <i>codesource</i>'s protocol.
  167. *
  168. * <li> If this object's host (getLocation().getHost()) is not null,
  169. * then the SocketPermission
  170. * constructed with this object's host must imply the
  171. * SocketPermission constructed with <i>codesource</i>'s host.
  172. *
  173. * <li> If this object's port (getLocation().getPort()) is not
  174. * equal to -1 (that is, if a port is specified), it must equal
  175. * <i>codesource</i>'s port.
  176. *
  177. * <li> If this object's file (getLocation().getFile()) doesn't equal
  178. * <i>codesource</i>'s file, then the following checks are made:
  179. * If this object's file ends with "/-",
  180. * then <i>codesource</i>'s file must start with this object's
  181. * file (exclusive the trailing "-").
  182. * If this object's file ends with a "/*",
  183. * then <i>codesource</i>'s file must start with this object's
  184. * file and must not have any further "/" separators.
  185. * If this object's file doesn't end with a "/",
  186. * then <i>codesource</i>'s file must match this object's
  187. * file with a '/' appended.
  188. *
  189. * <li> If this object's reference (getLocation().getRef()) is
  190. * not null, it must equal <i>codesource</i>'s reference.
  191. *
  192. * </ol>
  193. * </ol>
  194. * <p>
  195. * For example, the codesource objects with the following locations
  196. * and null certificates all imply
  197. * the codesource with the location "http://java.sun.com/classes/foo.jar"
  198. * and null certificates:
  199. * <pre>
  200. * http:
  201. * http://*.sun.com/classes/*
  202. * http://java.sun.com/classes/-
  203. * http://java.sun.com/classes/foo.jar
  204. * </pre>
  205. *
  206. * Note that if this CodeSource has a null location and a null
  207. * certificate chain, then it implies every other CodeSource.
  208. *
  209. * @param codesource CodeSource to compare against.
  210. *
  211. * @return true if the specified codesource is implied by this codesource,
  212. * false if not.
  213. */
  214. public boolean implies(CodeSource codesource)
  215. {
  216. if (codesource == null)
  217. return false;
  218. return matchCerts(codesource) && matchLocation(codesource);
  219. }
  220. /**
  221. * Returns true if all the certs in this
  222. * CodeSource are also in <i>that</i>.
  223. *
  224. * @param that the CodeSource to check against.
  225. */
  226. private boolean matchCerts(CodeSource that)
  227. {
  228. // match any key
  229. if (this.certs == null)
  230. return true;
  231. // if certs are null, and this.certs is not null, return false
  232. if (that.certs == null)
  233. return false;
  234. boolean match;
  235. for (int i=0; i < this.certs.length; i++) {
  236. match = false;
  237. for (int j=0; j < that.certs.length; j++) {
  238. if (this.certs[i].equals(that.certs[j])) {
  239. match = true;
  240. break;
  241. }
  242. }
  243. if (!match) return false;
  244. }
  245. return true;
  246. }
  247. /**
  248. * Returns true if two CodeSource's have the "same" location.
  249. *
  250. * @param that CodeSource to compare against
  251. */
  252. private boolean matchLocation(CodeSource that)
  253. {
  254. if (location == null) {
  255. return true;
  256. }
  257. if ((that == null) || (that.location == null))
  258. return false;
  259. if (location.equals(that.location))
  260. return true;
  261. if (!location.getProtocol().equals(that.location.getProtocol()))
  262. return false;
  263. if ((location.getHost() != null)) {
  264. if ((location.getHost().equals("") ||
  265. location.getHost().equals("localhost")) &&
  266. (that.location.getHost().equals("") ||
  267. that.location.getHost().equals("localhost"))) {
  268. // ok
  269. } else if (!location.getHost().equals(
  270. that.location.getHost())) {
  271. if (this.sp == null) {
  272. this.sp =
  273. new SocketPermission(location.getHost(),"resolve");
  274. }
  275. if (that.sp == null) {
  276. if (that.location.getHost() == null ||
  277. that.location.getHost().equals(""))
  278. return false;
  279. that.sp =
  280. new SocketPermission(that.location.getHost(),"resolve");
  281. }
  282. boolean ok = this.sp.implies(that.sp);
  283. if (!ok)
  284. return false;
  285. }
  286. }
  287. if (location.getPort() != -1) {
  288. if (location.getPort() != that.location.getPort())
  289. return false;
  290. }
  291. if (location.getFile().endsWith("/-")) {
  292. // Matches the directory and (recursively) all files
  293. // and subdirectories contained in that directory.
  294. // For example, "/a/b/-" implies anything that starts with
  295. // "/a/b/"
  296. String thisPath = location.getFile().substring(0,
  297. location.getFile().length()-1);
  298. if (!that.location.getFile().startsWith(thisPath))
  299. return false;
  300. } else if (location.getFile().endsWith("/*")) {
  301. // Matches the directory and all the files contained in that
  302. // directory.
  303. // For example, "/a/b/*" implies anything that starts with
  304. // "/a/b/" but has no further slashes
  305. int last = that.location.getFile().lastIndexOf('/');
  306. if (last == -1)
  307. return false;
  308. String thisPath = location.getFile().substring(0,
  309. location.getFile().length()-1);
  310. String thatPath = that.location.getFile().substring(0, last+1);
  311. if (!thatPath.equals(thisPath))
  312. return false;
  313. } else {
  314. // Exact matches only.
  315. // For example, "/a/b" and "/a/b/" both imply "/a/b/"
  316. if ((!that.location.getFile().equals(location.getFile()))
  317. && (!that.location.getFile().equals(location.getFile()+"/"))) {
  318. return false;
  319. }
  320. }
  321. if (location.getRef() == null)
  322. return true;
  323. else
  324. return location.getRef().equals(that.location.getRef());
  325. }
  326. /**
  327. * Returns a string describing this CodeSource, telling its
  328. * URL and certificates.
  329. *
  330. * @return information about this CodeSource.
  331. */
  332. public String toString() {
  333. StringBuffer sb = new StringBuffer();
  334. sb.append("(");
  335. sb.append(this.location);
  336. if (this.certs != null && this.certs.length > 0) {
  337. for (int i=0; i < this.certs.length; i++) {
  338. sb.append( " "+this.certs[i]);
  339. }
  340. } else {
  341. sb.append(" <no certificates>");
  342. }
  343. sb.append(")");
  344. return sb.toString();
  345. }
  346. /**
  347. * Writes this object out to a stream (i.e., serializes it).
  348. *
  349. * @serialData An initial <code>URL</code> is followed by an
  350. * <code>int</code> indicating the number of certificates to follow
  351. * (a value of "zero" denotes that there are no certificates associated
  352. * with this object).
  353. * Each certificate is written out starting with a <code>String</code>
  354. * denoting the certificate type, followed by an
  355. * <code>int</code> specifying the length of the certificate encoding,
  356. * followed by the certificate encoding itself which is written out as an
  357. * array of bytes.
  358. */
  359. private synchronized void writeObject(java.io.ObjectOutputStream oos)
  360. throws IOException
  361. {
  362. oos.defaultWriteObject();
  363. if (certs==null || certs.length==0) {
  364. oos.writeInt(0);
  365. } else {
  366. // write out the total number of certs
  367. oos.writeInt(certs.length);
  368. // write out each cert, including its type
  369. for (int i=0; i < certs.length; i++) {
  370. java.security.cert.Certificate cert = certs[i];
  371. try {
  372. oos.writeUTF(cert.getType());
  373. byte[] encoded = cert.getEncoded();
  374. oos.writeInt(encoded.length);
  375. oos.write(encoded);
  376. } catch (CertificateEncodingException cee) {
  377. throw new IOException(cee.getMessage());
  378. }
  379. }
  380. }
  381. }
  382. /**
  383. * Restores this object from a stream (i.e., deserializes it).
  384. */
  385. private synchronized void readObject(java.io.ObjectInputStream ois)
  386. throws IOException, ClassNotFoundException
  387. {
  388. CertificateFactory cf;
  389. Hashtable cfs=null;
  390. ois.defaultReadObject();
  391. // process any new-style certs in the stream (if present)
  392. int size = ois.readInt();
  393. if (size > 0) {
  394. // we know of 3 different cert types: X.509, PGP, SDSI, which
  395. // could all be present in the stream at the same time
  396. cfs = new Hashtable(3);
  397. this.certs = new java.security.cert.Certificate[size];
  398. }
  399. for (int i=0; i<size; i++) {
  400. // read the certificate type, and instantiate a certificate
  401. // factory of that type (reuse existing factory if possible)
  402. String certType = ois.readUTF();
  403. if (cfs.containsKey(certType)) {
  404. // reuse certificate factory
  405. cf = (CertificateFactory)cfs.get(certType);
  406. } else {
  407. // create new certificate factory
  408. try {
  409. cf = CertificateFactory.getInstance(certType);
  410. } catch (CertificateException ce) {
  411. throw new ClassNotFoundException
  412. ("Certificate factory for "+certType+" not found");
  413. }
  414. // store the certificate factory so we can reuse it later
  415. cfs.put(certType, cf);
  416. }
  417. // parse the certificate
  418. byte[] encoded=null;
  419. try {
  420. encoded = new byte[ois.readInt()];
  421. } catch (OutOfMemoryError oome) {
  422. throw new IOException("Certificate too big");
  423. }
  424. ois.readFully(encoded);
  425. ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
  426. try {
  427. this.certs[i] = cf.generateCertificate(bais);
  428. } catch (CertificateException ce) {
  429. throw new IOException(ce.getMessage());
  430. }
  431. bais.close();
  432. }
  433. }
  434. }