blob: 8b244e50fc1e7f393b77b0b002dc2273c869b3d6 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package java.security;
27
28
29import java.net.URL;
30import java.net.SocketPermission;
31import java.util.ArrayList;
32import java.util.List;
33import java.util.Hashtable;
34import java.io.ByteArrayInputStream;
35import java.io.IOException;
36import java.security.cert.*;
37
38/**
39 *
40 * <p>This class extends the concept of a codebase to
41 * encapsulate not only the location (URL) but also the certificate chains
42 * that were used to verify signed code originating from that location.
43 *
44 * @author Li Gong
45 * @author Roland Schemers
46 */
47
48public class CodeSource implements java.io.Serializable {
49
50 private static final long serialVersionUID = 4977541819976013951L;
51
52 /**
53 * The code location.
54 *
55 * @serial
56 */
57 private URL location;
58
59 /*
60 * The code signers.
61 */
62 private transient CodeSigner[] signers = null;
63
64 /*
65 * The code signers. Certificate chains are concatenated.
66 */
67 private transient java.security.cert.Certificate certs[] = null;
68
69 // cached SocketPermission used for matchLocation
70 private transient SocketPermission sp;
71
72 // for generating cert paths
73 private transient CertificateFactory factory = null;
74
75 /**
76 * Constructs a CodeSource and associates it with the specified
77 * location and set of certificates.
78 *
79 * @param url the location (URL).
80 *
81 * @param certs the certificate(s). It may be null. The contents of the
82 * array are copied to protect against subsequent modification.
83 */
84 public CodeSource(URL url, java.security.cert.Certificate certs[]) {
85 this.location = url;
86
87 // Copy the supplied certs
88 if (certs != null) {
89 this.certs = certs.clone();
90 }
91 }
92
93 /**
94 * Constructs a CodeSource and associates it with the specified
95 * location and set of code signers.
96 *
97 * @param url the location (URL).
98 * @param signers the code signers. It may be null. The contents of the
99 * array are copied to protect against subsequent modification.
100 *
101 * @since 1.5
102 */
103 public CodeSource(URL url, CodeSigner[] signers) {
104 this.location = url;
105
106 // Copy the supplied signers
107 if (signers != null) {
108 this.signers = signers.clone();
109 }
110 }
111
112 /**
113 * Returns the hash code value for this object.
114 *
115 * @return a hash code value for this object.
116 */
117
118 public int hashCode() {
119 if (location != null)
120 return location.hashCode();
121 else
122 return 0;
123 }
124
125 /**
126 * Tests for equality between the specified object and this
127 * object. Two CodeSource objects are considered equal if their
128 * locations are of identical value and if their signer certificate
129 * chains are of identical value. It is not required that
130 * the certificate chains be in the same order.
131 *
132 * @param obj the object to test for equality with this object.
133 *
134 * @return true if the objects are considered equal, false otherwise.
135 */
136 public boolean equals(Object obj) {
137 if (obj == this)
138 return true;
139
140 // objects types must be equal
141 if (!(obj instanceof CodeSource))
142 return false;
143
144 CodeSource cs = (CodeSource) obj;
145
146 // URLs must match
147 if (location == null) {
148 // if location is null, then cs.location must be null as well
149 if (cs.location != null) return false;
150 } else {
151 // if location is not null, then it must equal cs.location
152 if (!location.equals(cs.location)) return false;
153 }
154
155 // certs must match
156 return matchCerts(cs, true);
157 }
158
159 /**
160 * Returns the location associated with this CodeSource.
161 *
162 * @return the location (URL).
163 */
164 public final URL getLocation() {
165 /* since URL is practically immutable, returning itself is not
166 a security problem */
167 return this.location;
168 }
169
170 /**
171 * Returns the certificates associated with this CodeSource.
172 * <p>
173 * If this CodeSource object was created using the
174 * {@link #CodeSource(URL url, CodeSigner[] signers)}
175 * constructor then its certificate chains are extracted and used to
176 * create an array of Certificate objects. Each signer certificate is
177 * followed by its supporting certificate chain (which may be empty).
178 * Each signer certificate and its supporting certificate chain is ordered
179 * bottom-to-top (i.e., with the signer certificate first and the (root)
180 * certificate authority last).
181 *
182 * @return A copy of the certificates array, or null if there is none.
183 */
184 public final java.security.cert.Certificate[] getCertificates() {
185 if (certs != null) {
186 return certs.clone();
187
188 } else if (signers != null) {
189 // Convert the code signers to certs
190 ArrayList<java.security.cert.Certificate> certChains =
191 new ArrayList<java.security.cert.Certificate>();
192 for (int i = 0; i < signers.length; i++) {
193 certChains.addAll(
194 signers[i].getSignerCertPath().getCertificates());
195 }
196 certs = certChains.toArray(
197 new java.security.cert.Certificate[certChains.size()]);
198 return certs.clone();
199
200 } else {
201 return null;
202 }
203 }
204
205 /**
206 * Returns the code signers associated with this CodeSource.
207 * <p>
208 * If this CodeSource object was created using the
209 * {@link #CodeSource(URL url, Certificate[] certs)}
210 * constructor then its certificate chains are extracted and used to
211 * create an array of CodeSigner objects. Note that only X.509 certificates
212 * are examined - all other certificate types are ignored.
213 *
214 * @return A copy of the code signer array, or null if there is none.
215 *
216 * @since 1.5
217 */
218 public final CodeSigner[] getCodeSigners() {
219 if (signers != null) {
220 return signers.clone();
221
222 } else if (certs != null) {
223 // Convert the certs to code signers
224 signers = convertCertArrayToSignerArray(certs);
225 return signers.clone();
226
227 } else {
228 return null;
229 }
230 }
231
232 /**
233 * Returns true if this CodeSource object "implies" the specified CodeSource.
234 * <P>
235 * More specifically, this method makes the following checks, in order.
236 * If any fail, it returns false. If they all succeed, it returns true.<p>
237 * <ol>
238 * <li> <i>codesource</i> must not be null.
239 * <li> If this object's certificates are not null, then all
240 * of this object's certificates must be present in <i>codesource</i>'s
241 * certificates.
242 * <li> If this object's location (getLocation()) is not null, then the
243 * following checks are made against this object's location and
244 * <i>codesource</i>'s:<p>
245 * <ol>
246 * <li> <i>codesource</i>'s location must not be null.
247 *
248 * <li> If this object's location
249 * equals <i>codesource</i>'s location, then return true.
250 *
251 * <li> This object's protocol (getLocation().getProtocol()) must be
252 * equal to <i>codesource</i>'s protocol.
253 *
254 * <li> If this object's host (getLocation().getHost()) is not null,
255 * then the SocketPermission
256 * constructed with this object's host must imply the
257 * SocketPermission constructed with <i>codesource</i>'s host.
258 *
259 * <li> If this object's port (getLocation().getPort()) is not
260 * equal to -1 (that is, if a port is specified), it must equal
261 * <i>codesource</i>'s port.
262 *
263 * <li> If this object's file (getLocation().getFile()) doesn't equal
264 * <i>codesource</i>'s file, then the following checks are made:
265 * If this object's file ends with "/-",
266 * then <i>codesource</i>'s file must start with this object's
267 * file (exclusive the trailing "-").
268 * If this object's file ends with a "/*",
269 * then <i>codesource</i>'s file must start with this object's
270 * file and must not have any further "/" separators.
271 * If this object's file doesn't end with a "/",
272 * then <i>codesource</i>'s file must match this object's
273 * file with a '/' appended.
274 *
275 * <li> If this object's reference (getLocation().getRef()) is
276 * not null, it must equal <i>codesource</i>'s reference.
277 *
278 * </ol>
279 * </ol>
280 * <p>
281 * For example, the codesource objects with the following locations
282 * and null certificates all imply
283 * the codesource with the location "http://java.sun.com/classes/foo.jar"
284 * and null certificates:
285 * <pre>
286 * http:
287 * http://*.sun.com/classes/*
288 * http://java.sun.com/classes/-
289 * http://java.sun.com/classes/foo.jar
290 * </pre>
291 *
292 * Note that if this CodeSource has a null location and a null
293 * certificate chain, then it implies every other CodeSource.
294 *
295 * @param codesource CodeSource to compare against.
296 *
297 * @return true if the specified codesource is implied by this codesource,
298 * false if not.
299 */
300
301 public boolean implies(CodeSource codesource)
302 {
303 if (codesource == null)
304 return false;
305
306 return matchCerts(codesource, false) && matchLocation(codesource);
307 }
308
309 /**
310 * Returns true if all the certs in this
311 * CodeSource are also in <i>that</i>.
312 *
313 * @param that the CodeSource to check against.
314 * @param strict If true then a strict equality match is performed.
315 * Otherwise a subset match is performed.
316 */
317 private boolean matchCerts(CodeSource that, boolean strict)
318 {
319 boolean match;
320
321 // match any key
322 if (certs == null && signers == null) {
323 if (strict) {
324 return (that.certs == null && that.signers == null);
325 } else {
326 return true;
327 }
328 // both have signers
329 } else if (signers != null && that.signers != null) {
330 if (strict && signers.length != that.signers.length) {
331 return false;
332 }
333 for (int i = 0; i < signers.length; i++) {
334 match = false;
335 for (int j = 0; j < that.signers.length; j++) {
336 if (signers[i].equals(that.signers[j])) {
337 match = true;
338 break;
339 }
340 }
341 if (!match) return false;
342 }
343 return true;
344
345 // both have certs
346 } else if (certs != null && that.certs != null) {
347 if (strict && certs.length != that.certs.length) {
348 return false;
349 }
350 for (int i = 0; i < certs.length; i++) {
351 match = false;
352 for (int j = 0; j < that.certs.length; j++) {
353 if (certs[i].equals(that.certs[j])) {
354 match = true;
355 break;
356 }
357 }
358 if (!match) return false;
359 }
360 return true;
361 }
362
363 return false;
364 }
365
366
367 /**
368 * Returns true if two CodeSource's have the "same" location.
369 *
370 * @param that CodeSource to compare against
371 */
372 private boolean matchLocation(CodeSource that)
373 {
374 if (location == null) {
375 return true;
376 }
377
378 if ((that == null) || (that.location == null))
379 return false;
380
381 if (location.equals(that.location))
382 return true;
383
384 if (!location.getProtocol().equals(that.location.getProtocol()))
385 return false;
386
387 String thisHost = location.getHost();
388 String thatHost = that.location.getHost();
389
390 if (thisHost != null) {
391 if (("".equals(thisHost) || "localhost".equals(thisHost)) &&
392 ("".equals(thatHost) || "localhost".equals(thatHost))) {
393 // ok
394 } else if (!thisHost.equals(thatHost)) {
395 if (thatHost == null) {
396 return false;
397 }
398 if (this.sp == null) {
399 this.sp = new SocketPermission(thisHost, "resolve");
400 }
401 if (that.sp == null) {
402 that.sp = new SocketPermission(thatHost, "resolve");
403 }
404 if (!this.sp.implies(that.sp)) {
405 return false;
406 }
407 }
408 }
409
410 if (location.getPort() != -1) {
411 if (location.getPort() != that.location.getPort())
412 return false;
413 }
414
415 if (location.getFile().endsWith("/-")) {
416 // Matches the directory and (recursively) all files
417 // and subdirectories contained in that directory.
418 // For example, "/a/b/-" implies anything that starts with
419 // "/a/b/"
420 String thisPath = location.getFile().substring(0,
421 location.getFile().length()-1);
422 if (!that.location.getFile().startsWith(thisPath))
423 return false;
424 } else if (location.getFile().endsWith("/*")) {
425 // Matches the directory and all the files contained in that
426 // directory.
427 // For example, "/a/b/*" implies anything that starts with
428 // "/a/b/" but has no further slashes
429 int last = that.location.getFile().lastIndexOf('/');
430 if (last == -1)
431 return false;
432 String thisPath = location.getFile().substring(0,
433 location.getFile().length()-1);
434 String thatPath = that.location.getFile().substring(0, last+1);
435 if (!thatPath.equals(thisPath))
436 return false;
437 } else {
438 // Exact matches only.
439 // For example, "/a/b" and "/a/b/" both imply "/a/b/"
440 if ((!that.location.getFile().equals(location.getFile()))
441 && (!that.location.getFile().equals(location.getFile()+"/"))) {
442 return false;
443 }
444 }
445
446 if (location.getRef() == null)
447 return true;
448 else
449 return location.getRef().equals(that.location.getRef());
450 }
451
452 /**
453 * Returns a string describing this CodeSource, telling its
454 * URL and certificates.
455 *
456 * @return information about this CodeSource.
457 */
458 public String toString() {
459 StringBuilder sb = new StringBuilder();
460 sb.append("(");
461 sb.append(this.location);
462
463 if (this.certs != null && this.certs.length > 0) {
464 for (int i = 0; i < this.certs.length; i++) {
465 sb.append( " " + this.certs[i]);
466 }
467
468 } else if (this.signers != null && this.signers.length > 0) {
469 for (int i = 0; i < this.signers.length; i++) {
470 sb.append( " " + this.signers[i]);
471 }
472 } else {
473 sb.append(" <no signer certificates>");
474 }
475 sb.append(")");
476 return sb.toString();
477 }
478
479 /**
480 * Writes this object out to a stream (i.e., serializes it).
481 *
482 * @serialData An initial <code>URL</code> is followed by an
483 * <code>int</code> indicating the number of certificates to follow
484 * (a value of "zero" denotes that there are no certificates associated
485 * with this object).
486 * Each certificate is written out starting with a <code>String</code>
487 * denoting the certificate type, followed by an
488 * <code>int</code> specifying the length of the certificate encoding,
489 * followed by the certificate encoding itself which is written out as an
490 * array of bytes. Finally, if any code signers are present then the array
491 * of code signers is serialized and written out too.
492 */
493 private void writeObject(java.io.ObjectOutputStream oos)
494 throws IOException
495 {
496 oos.defaultWriteObject(); // location
497
498 // Serialize the array of certs
499 if (certs == null || certs.length == 0) {
500 oos.writeInt(0);
501 } else {
502 // write out the total number of certs
503 oos.writeInt(certs.length);
504 // write out each cert, including its type
505 for (int i = 0; i < certs.length; i++) {
506 java.security.cert.Certificate cert = certs[i];
507 try {
508 oos.writeUTF(cert.getType());
509 byte[] encoded = cert.getEncoded();
510 oos.writeInt(encoded.length);
511 oos.write(encoded);
512 } catch (CertificateEncodingException cee) {
513 throw new IOException(cee.getMessage());
514 }
515 }
516 }
517
518 // Serialize the array of code signers (if any)
519 if (signers != null && signers.length > 0) {
520 oos.writeObject(signers);
521 }
522 }
523
524 /**
525 * Restores this object from a stream (i.e., deserializes it).
526 */
527 private void readObject(java.io.ObjectInputStream ois)
528 throws IOException, ClassNotFoundException
529 {
530 CertificateFactory cf;
531 Hashtable<String, CertificateFactory> cfs = null;
532
533 ois.defaultReadObject(); // location
534
535 // process any new-style certs in the stream (if present)
536 int size = ois.readInt();
537 if (size > 0) {
538 // we know of 3 different cert types: X.509, PGP, SDSI, which
539 // could all be present in the stream at the same time
540 cfs = new Hashtable<String, CertificateFactory>(3);
541 this.certs = new java.security.cert.Certificate[size];
542 }
543
544 for (int i = 0; i < size; i++) {
545 // read the certificate type, and instantiate a certificate
546 // factory of that type (reuse existing factory if possible)
547 String certType = ois.readUTF();
548 if (cfs.containsKey(certType)) {
549 // reuse certificate factory
550 cf = cfs.get(certType);
551 } else {
552 // create new certificate factory
553 try {
554 cf = CertificateFactory.getInstance(certType);
555 } catch (CertificateException ce) {
556 throw new ClassNotFoundException
557 ("Certificate factory for " + certType + " not found");
558 }
559 // store the certificate factory so we can reuse it later
560 cfs.put(certType, cf);
561 }
562 // parse the certificate
563 byte[] encoded = null;
564 try {
565 encoded = new byte[ois.readInt()];
566 } catch (OutOfMemoryError oome) {
567 throw new IOException("Certificate too big");
568 }
569 ois.readFully(encoded);
570 ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
571 try {
572 this.certs[i] = cf.generateCertificate(bais);
573 } catch (CertificateException ce) {
574 throw new IOException(ce.getMessage());
575 }
576 bais.close();
577 }
578
579 // Deserialize array of code signers (if any)
580 try {
581 this.signers = (CodeSigner[])ois.readObject();
582 } catch (IOException ioe) {
583 // no signers present
584 }
585 }
586
587 /*
588 * Convert an array of certificates to an array of code signers.
589 * The array of certificates is a concatenation of certificate chains
590 * where the initial certificate in each chain is the end-entity cert.
591 *
592 * @return An array of code signers or null if none are generated.
593 */
594 private CodeSigner[] convertCertArrayToSignerArray(
595 java.security.cert.Certificate[] certs) {
596
597 if (certs == null) {
598 return null;
599 }
600
601 try {
602 // Initialize certificate factory
603 if (factory == null) {
604 factory = CertificateFactory.getInstance("X.509");
605 }
606
607 // Iterate through all the certificates
608 int i = 0;
609 List<CodeSigner> signers = new ArrayList<CodeSigner>();
610 while (i < certs.length) {
611 List<java.security.cert.Certificate> certChain =
612 new ArrayList<java.security.cert.Certificate>();
613 certChain.add(certs[i++]); // first cert is an end-entity cert
614 int j = i;
615
616 // Extract chain of certificates
617 // (loop while certs are not end-entity certs)
618 while (j < certs.length &&
619 certs[j] instanceof X509Certificate &&
620 ((X509Certificate)certs[j]).getBasicConstraints() != -1) {
621 certChain.add(certs[j]);
622 j++;
623 }
624 i = j;
625 CertPath certPath = factory.generateCertPath(certChain);
626 signers.add(new CodeSigner(certPath, null));
627 }
628
629 if (signers.isEmpty()) {
630 return null;
631 } else {
632 return signers.toArray(new CodeSigner[signers.size()]);
633 }
634
635 } catch (CertificateException e) {
636 return null; //TODO - may be better to throw an ex. here
637 }
638 }
639}