1. /*
  2. * @(#)CodeSource.java 1.27 01/11/29
  3. *
  4. * Copyright 2002 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.27, 11/29/01
  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. * @param codesource CodeSource to compare against.
  207. *
  208. * @return true if the specified codesource is implied by this codesource,
  209. * false if not.
  210. */
  211. public boolean implies(CodeSource codesource)
  212. {
  213. if (codesource == null)
  214. return false;
  215. return matchCerts(codesource) && matchLocation(codesource);
  216. }
  217. /**
  218. * Returns true if all the certs in this
  219. * CodeSource are also in <i>that</i>.
  220. *
  221. * @param that the CodeSource to check against.
  222. */
  223. private boolean matchCerts(CodeSource that)
  224. {
  225. // match any key
  226. if (this.certs == null)
  227. return true;
  228. // if certs are null, and this.certs is not null, return false
  229. if (that.certs == null)
  230. return false;
  231. boolean match;
  232. for (int i=0; i < this.certs.length; i++) {
  233. match = false;
  234. for (int j=0; j < that.certs.length; j++) {
  235. if (this.certs[i].equals(that.certs[j])) {
  236. match = true;
  237. break;
  238. }
  239. }
  240. if (!match) return false;
  241. }
  242. return true;
  243. }
  244. /**
  245. * Returns true if two CodeSource's have the "same" location.
  246. *
  247. * @param that CodeSource to compare against
  248. */
  249. private boolean matchLocation(CodeSource that)
  250. {
  251. if (location == null) {
  252. return true;
  253. }
  254. if ((that == null) || (that.location == null))
  255. return false;
  256. if (location.equals(that.location))
  257. return true;
  258. if (!location.getProtocol().equals(that.location.getProtocol()))
  259. return false;
  260. if ((location.getHost() != null)) {
  261. if ((location.getHost().equals("") ||
  262. location.getHost().equals("localhost")) &&
  263. (that.location.getHost().equals("") ||
  264. that.location.getHost().equals("localhost"))) {
  265. // ok
  266. } else if (!location.getHost().equals(
  267. that.location.getHost())) {
  268. if (this.sp == null) {
  269. this.sp =
  270. new SocketPermission(location.getHost(),"resolve");
  271. }
  272. if (that.sp == null) {
  273. if (that.location.getHost() == null ||
  274. that.location.getHost().equals(""))
  275. return false;
  276. that.sp =
  277. new SocketPermission(that.location.getHost(),"resolve");
  278. }
  279. boolean ok = this.sp.implies(that.sp);
  280. if (!ok)
  281. return false;
  282. }
  283. }
  284. if (location.getPort() != -1) {
  285. if (location.getPort() != that.location.getPort())
  286. return false;
  287. }
  288. if (location.getFile().endsWith("/-")) {
  289. // Matches the directory and (recursively) all files
  290. // and subdirectories contained in that directory.
  291. // For example, "/a/b/-" implies anything that starts with
  292. // "/a/b/"
  293. String thisPath = location.getFile().substring(0,
  294. location.getFile().length()-1);
  295. if (!that.location.getFile().startsWith(thisPath))
  296. return false;
  297. } else if (location.getFile().endsWith("/*")) {
  298. // Matches the directory and all the files contained in that
  299. // directory.
  300. // For example, "/a/b/*" implies anything that starts with
  301. // "/a/b/" but has no further slashes
  302. int last = that.location.getFile().lastIndexOf('/');
  303. if (last == -1)
  304. return false;
  305. String thisPath = location.getFile().substring(0,
  306. location.getFile().length()-1);
  307. String thatPath = that.location.getFile().substring(0, last+1);
  308. if (!thatPath.equals(thisPath))
  309. return false;
  310. } else {
  311. // Exact matches only.
  312. // For example, "/a/b" and "/a/b/" both imply "/a/b/"
  313. if ((!that.location.getFile().equals(location.getFile()))
  314. && (!that.location.getFile().equals(location.getFile()+"/"))) {
  315. return false;
  316. }
  317. }
  318. if (location.getRef() == null)
  319. return true;
  320. else
  321. return location.getRef().equals(that.location.getRef());
  322. }
  323. /**
  324. * Returns a string describing this CodeSource, telling its
  325. * URL and certificates.
  326. *
  327. * @return information about this CodeSource.
  328. */
  329. public String toString() {
  330. StringBuffer sb = new StringBuffer();
  331. sb.append("(");
  332. sb.append(this.location);
  333. if (this.certs != null && this.certs.length > 0) {
  334. for (int i=0; i < this.certs.length; i++) {
  335. sb.append( " "+this.certs[i]);
  336. }
  337. } else {
  338. sb.append(" <no certificates>");
  339. }
  340. sb.append(")");
  341. return sb.toString();
  342. }
  343. /**
  344. * Writes this object out to a stream (i.e., serializes it).
  345. *
  346. * @serialData An initial <code>URL</code> is followed by an
  347. * <code>int</code> indicating the number of certificates to follow
  348. * (a value of "zero" denotes that there are no certificates associated
  349. * with this object).
  350. * Each certificate is written out starting with a <code>String</code>
  351. * denoting the certificate type, followed by an
  352. * <code>int</code> specifying the length of the certificate encoding,
  353. * followed by the certificate encoding itself which is written out as an
  354. * array of bytes.
  355. */
  356. private synchronized void writeObject(java.io.ObjectOutputStream oos)
  357. throws IOException
  358. {
  359. oos.defaultWriteObject();
  360. if (certs==null || certs.length==0) {
  361. oos.writeInt(0);
  362. } else {
  363. // write out the total number of certs
  364. oos.writeInt(certs.length);
  365. // write out each cert, including its type
  366. for (int i=0; i < certs.length; i++) {
  367. java.security.cert.Certificate cert = certs[i];
  368. try {
  369. oos.writeUTF(cert.getType());
  370. byte[] encoded = cert.getEncoded();
  371. oos.writeInt(encoded.length);
  372. oos.write(encoded);
  373. } catch (CertificateEncodingException cee) {
  374. throw new IOException(cee.getMessage());
  375. }
  376. }
  377. }
  378. }
  379. /**
  380. * Restores this object from a stream (i.e., deserializes it).
  381. */
  382. private synchronized void readObject(java.io.ObjectInputStream ois)
  383. throws IOException, ClassNotFoundException
  384. {
  385. CertificateFactory cf;
  386. Hashtable cfs=null;
  387. ois.defaultReadObject();
  388. // process any new-style certs in the stream (if present)
  389. int size = ois.readInt();
  390. if (size > 0) {
  391. // we know of 3 different cert types: X.509, PGP, SDSI, which
  392. // could all be present in the stream at the same time
  393. cfs = new Hashtable(3);
  394. this.certs = new java.security.cert.Certificate[size];
  395. }
  396. for (int i=0; i<size; i++) {
  397. // read the certificate type, and instantiate a certificate
  398. // factory of that type (reuse existing factory if possible)
  399. String certType = ois.readUTF();
  400. if (cfs.containsKey(certType)) {
  401. // reuse certificate factory
  402. cf = (CertificateFactory)cfs.get(certType);
  403. } else {
  404. // create new certificate factory
  405. try {
  406. cf = CertificateFactory.getInstance(certType);
  407. } catch (CertificateException ce) {
  408. throw new ClassNotFoundException
  409. ("Certificate factory for "+certType+" not found");
  410. }
  411. // store the certificate factory so we can reuse it later
  412. cfs.put(certType, cf);
  413. }
  414. // parse the certificate
  415. byte[] encoded=null;
  416. try {
  417. encoded = new byte[ois.readInt()];
  418. } catch (OutOfMemoryError oome) {
  419. throw new IOException("Certificate too big");
  420. }
  421. ois.readFully(encoded);
  422. ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
  423. try {
  424. this.certs[i] = cf.generateCertificate(bais);
  425. } catch (CertificateException ce) {
  426. throw new IOException(ce.getMessage());
  427. }
  428. bais.close();
  429. }
  430. }
  431. }