001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Alexander V. Astapchuk
020: * @version $Revision$
021: */package java.security;
022:
023: import java.io.ByteArrayInputStream;
024: import java.io.IOException;
025: import java.io.OptionalDataException;
026: import java.io.ObjectInputStream;
027: import java.io.ObjectOutputStream;
028: import java.io.Serializable;
029: import java.net.SocketPermission;
030: import java.net.URL;
031: import java.security.cert.CertPath;
032: import java.security.cert.Certificate;
033: import java.security.cert.CertificateEncodingException;
034: import java.security.cert.CertificateException;
035: import java.security.cert.CertificateFactory;
036: import java.security.cert.X509Certificate;
037: import java.util.ArrayList;
038: import java.util.List;
039:
040: import javax.security.auth.x500.X500Principal;
041:
042: import org.apache.harmony.security.fortress.PolicyUtils;
043: import org.apache.harmony.security.internal.nls.Messages;
044:
045: public class CodeSource implements Serializable {
046:
047: private static final long serialVersionUID = 4977541819976013951L;
048:
049: // Location of this CodeSource object
050: private URL location;
051:
052: // Array of certificates assigned to this CodeSource object
053: private transient java.security.cert.Certificate[] certs;
054:
055: // Array of CodeSigners
056: private transient CodeSigner[] signers;
057:
058: // SocketPermission() in implies() method takes to many time.
059: // Need to cache it for better performance.
060: private transient SocketPermission sp;
061:
062: // Cached factory used to build CertPath-s in <code>getCodeSigners()</code>.
063: private transient CertificateFactory factory;
064:
065: /**
066: * Constructs a new instance of this class with its url and certificates
067: * fields filled in from the arguments.
068: *
069: * @param location
070: * URL the URL.
071: * @param certs
072: * Certificate[] the Certificates.
073: */
074: public CodeSource(URL location, Certificate[] certs) {
075: this .location = location;
076: if (certs != null) {
077: this .certs = new Certificate[certs.length];
078: System.arraycopy(certs, 0, this .certs, 0, certs.length);
079: }
080: }
081:
082: public CodeSource(URL location, CodeSigner[] signers) {
083: this .location = location;
084: if (signers != null) {
085: this .signers = new CodeSigner[signers.length];
086: System.arraycopy(signers, 0, this .signers, 0,
087: signers.length);
088: }
089: }
090:
091: /**
092: * Compares the argument to the receiver, and answers true if they represent
093: * the <em>same</em> object using a class specific comparison. In this
094: * case, the receiver and the object must have the same URL and the same
095: * collection of certificates.
096: *
097: *
098: * @param obj
099: * the object to compare with this object
100: * @return <code>true</code> if the object is the same as this object
101: * <code>false</code> if it is different from this object
102: * @see #hashCode
103: */
104: public boolean equals(Object obj) {
105: if (obj == this ) {
106: return true;
107: }
108:
109: if (!(obj instanceof CodeSource)) {
110: return false;
111: }
112:
113: CodeSource that = (CodeSource) obj;
114:
115: if (this .location != null) {
116: if (that.location == null) {
117: return false;
118: }
119: if (!this .location.equals(that.location)) {
120: return false;
121: }
122: } else if (that.location != null) {
123: return false;
124: }
125:
126: // do not use this.certs, as we also need to take care about
127: // CodeSigners' certificates
128: Certificate[] thizCerts = getCertificatesNoClone();
129: Certificate[] thatCerts = that.getCertificatesNoClone();
130: if (!PolicyUtils.matchSubset(thizCerts, thatCerts)) {
131: return false;
132: }
133: if (!PolicyUtils.matchSubset(thatCerts, thizCerts)) {
134: return false;
135: }
136: return true;
137: }
138:
139: /**
140: * Answers the certificates held onto by the receiver.
141: *
142: *
143: * @return Certificate[] the receiver's certificates
144: */
145: public final Certificate[] getCertificates() {
146: getCertificatesNoClone();
147: if (certs == null) {
148: return null;
149: }
150: Certificate[] tmp = new Certificate[certs.length];
151: System.arraycopy(certs, 0, tmp, 0, certs.length);
152: return tmp;
153: }
154:
155: // Acts exactly as {@link #getCertificates()} does, but does not clone the
156: // array before returning (and returns reference to <code>this.certs</code>
157: // if this array is not null).<br>
158: // @return a reference to the certificates array, or null if there are no
159: // certificates associated.
160: private Certificate[] getCertificatesNoClone() {
161: if (certs != null) {
162: return certs;
163: }
164:
165: if (signers == null) {
166: return null;
167: }
168: // Extract Certificates from the CodeSigner-s
169: ArrayList<Certificate> v = new ArrayList<Certificate>();
170: for (int i = 0; i < signers.length; i++) {
171: v.addAll(signers[i].getSignerCertPath().getCertificates());
172: }
173:
174: certs = v.toArray(new Certificate[v.size()]);
175: return certs;
176: }
177:
178: public final CodeSigner[] getCodeSigners() {
179: if (signers != null) {
180: CodeSigner[] tmp = new CodeSigner[signers.length];
181: System.arraycopy(signers, 0, tmp, 0, tmp.length);
182: return tmp;
183: }
184: if (certs == null || factory != null) {
185: // factory != null means we've done this exercise already.
186: return null;
187: }
188:
189: X500Principal prevIssuer = null;
190: ArrayList<Certificate> list = new ArrayList<Certificate>(
191: certs.length);
192: ArrayList<CodeSigner> asigners = new ArrayList<CodeSigner>();
193:
194: // The presumption is that the chains of certificates are placed
195: // according to the CertPath agreement:
196: //
197: // the lowest certs first; the CAs are at the last
198: //
199: // So the following loop scans trough the certs and checks
200: // that every next certificate is an Issuer of the previous one.
201: // Any certificate that is not an Issuer of the previous one starts a
202: // new chain (== a new CertPath)
203:
204: for (int i = 0; i < certs.length; i++) {
205: if (!(certs[i] instanceof X509Certificate)) {
206: // Only X509Certificate-s are taken into account - see API spec.
207: continue;
208: }
209: X509Certificate x509 = (X509Certificate) certs[i];
210: if (prevIssuer == null) {
211: // start a very first chain
212: prevIssuer = x509.getIssuerX500Principal();
213: list.add(x509);
214: } else {
215: X500Principal subj = x509.getSubjectX500Principal();
216: if (!prevIssuer.equals(subj)) {
217: // Ok, this ends the previous chain,
218: // so transform this one into CertPath ...
219: CertPath cpath = makeCertPath(list);
220: if (cpath != null) {
221: asigners.add(new CodeSigner(cpath, null));
222: }
223: // ... and start a new one
224: list.clear();
225: }// else { it's still the same chain }
226: prevIssuer = x509.getSubjectX500Principal();
227: list.add(x509);
228: }
229: }
230: if (!list.isEmpty()) {
231: CertPath cpath = makeCertPath(list);
232: if (cpath != null) {
233: asigners.add(new CodeSigner(cpath, null));
234: }
235: }
236: if (asigners.isEmpty()) {
237: // 'signers' is 'null' already
238: return null;
239: }
240: signers = new CodeSigner[asigners.size()];
241: asigners.toArray(signers);
242: CodeSigner[] tmp = new CodeSigner[asigners.size()];
243: System.arraycopy(signers, 0, tmp, 0, tmp.length);
244: return tmp;
245: }
246:
247: // Makes an CertPath from a given List of X509Certificate-s.
248: // @param list
249: // @return CertPath, or null if CertPath cannot be made
250: private CertPath makeCertPath(List<? extends Certificate> list) {
251: if (factory == null) {
252: try {
253: factory = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
254: } catch (CertificateException ex) {
255: //? throw new Error("X.509 is a 'must be'", ex);
256: return null;
257: }
258: }
259: try {
260: return factory.generateCertPath(list);
261: } catch (CertificateException ex) {
262: // ignore(ex)
263: }
264: return null;
265: }
266:
267: /**
268: * Answers the receiver's location.
269: *
270: *
271: * @return URL the receiver's URL
272: */
273: public final URL getLocation() {
274: return location;
275: }
276:
277: /**
278: * Answers an integer hash code for the receiver. Any two objects which
279: * answer <code>true</code> when passed to <code>.equals</code> must
280: * answer the same value for this method.
281: *
282: *
283: * @return int the receiver's hash.
284: *
285: * @see #equals
286: */
287: public int hashCode() {
288: //
289: // hashCode() is undocumented there. Should we also use certs[i] to
290: // compute the hash ?
291: // for now, I don't take certs[] into account
292: return location == null ? 0 : location.hashCode();
293: }
294:
295: /**
296: * Indicates whether the argument code source is implied by the receiver.
297: *
298: *
299: * @return boolean <code>true</code> if the argument code source is
300: * implied by the receiver, and <code>false</code> if it is not.
301: * @param cs
302: * CodeSource the code source to check
303: */
304: public boolean implies(CodeSource cs) {
305: //
306: // Here, javadoc:N refers to the appropriate item in the API spec for
307: // the CodeSource.implies()
308: // The info was taken from the 1.5 final API spec
309:
310: // javadoc:1
311: if (cs == null) {
312: return false;
313: }
314:
315: // javadoc:2
316: // with a comment: the javadoc says only about certificates and does
317: // not explicitly mention CodeSigners' certs.
318: // It seems more convenient to use getCerts() to get the real
319: // certificates - with a certificates got form the signers
320: Certificate[] thizCerts = getCertificatesNoClone();
321: if (thizCerts != null) {
322: Certificate[] thatCerts = cs.getCertificatesNoClone();
323: if (thatCerts == null
324: || !PolicyUtils.matchSubset(thizCerts, thatCerts)) {
325: return false;
326: }
327: }
328:
329: // javadoc:3
330: if (this .location != null) {
331: //javadoc:3.1
332: if (cs.location == null) {
333: return false;
334: }
335: //javadoc:3.2
336: if (this .location.equals(cs.location)) {
337: return true;
338: }
339: //javadoc:3.3
340: if (!this .location.getProtocol().equals(
341: cs.location.getProtocol())) {
342: return false;
343: }
344: //javadoc:3.4
345: String this Host = this .location.getHost();
346: if (this Host != null) {
347: String thatHost = cs.location.getHost();
348: if (thatHost == null) {
349: return false;
350: }
351:
352: // 1. According to the spec, an empty string will be considered
353: // as "localhost" in the SocketPermission
354: // 2. 'file://' URLs will have an empty getHost()
355: // so, let's make a special processing of localhost-s, I do
356: // believe this'll improve performance of file:// code sources
357:
358: //
359: // Don't have to evaluate both the boolean-s each time.
360: // It's better to evaluate them directly under if() statement.
361: //
362: // boolean thisIsLocalHost = thisHost.length() == 0 || "localhost".equals(thisHost);
363: // boolean thatIsLocalHost = thatHost.length() == 0 || "localhost".equals(thatHost);
364: //
365: // if( !(thisIsLocalHost && thatIsLocalHost) &&
366: // !thisHost.equals(thatHost)) {
367:
368: if (!((this Host.length() == 0 || "localhost".equals(this Host)) && (thatHost //$NON-NLS-1$
369: .length() == 0 || "localhost".equals(thatHost))) //$NON-NLS-1$
370: && !this Host.equals(thatHost)) {
371:
372: // Obvious, but very slow way....
373: //
374: // SocketPermission thisPerm = new SocketPermission(
375: // this.location.getHost(), "resolve");
376: // SocketPermission thatPerm = new SocketPermission(
377: // cs.location.getHost(), "resolve");
378: // if (!thisPerm.implies(thatPerm)) {
379: // return false;
380: // }
381: //
382: // let's cache it:
383:
384: if (this .sp == null) {
385: this .sp = new SocketPermission(this Host,
386: "resolve"); //$NON-NLS-1$
387: }
388:
389: if (cs.sp == null) {
390: cs.sp = new SocketPermission(thatHost,
391: "resolve"); //$NON-NLS-1$
392: }
393:
394: if (!this .sp.implies(cs.sp)) {
395: return false;
396: }
397: } // if( ! this.location.getHost().equals(cs.location.getHost())
398: } // if (this.location.getHost() != null)
399:
400: //javadoc:3.5
401: if (this .location.getPort() != -1) {
402: if (this .location.getPort() != cs.location.getPort()) {
403: return false;
404: }
405: }
406:
407: //javadoc:3.6
408: String this File = this .location.getFile();
409: String thatFile = cs.location.getFile();
410:
411: if (this File.endsWith("/-")) { //javadoc:3.6."/-" //$NON-NLS-1$
412: if (!thatFile.startsWith(this File.substring(0, this File
413: .length() - 2))) {
414: return false;
415: }
416: } else if (this File.endsWith("/*")) { //javadoc:3.6."/*" //$NON-NLS-1$
417: if (!thatFile.startsWith(this File.substring(0, this File
418: .length() - 2))) {
419: return false;
420: }
421: // no further separators(s) allowed
422: if (thatFile.indexOf("/", this File.length() - 1) != -1) { //$NON-NLS-1$
423: return false;
424: }
425: } else {
426: // javadoc:3.6."/"
427: if (!this File.equals(thatFile)) {
428: if (!this File.endsWith("/")) { //$NON-NLS-1$
429: if (!thatFile.equals(this File + "/")) { //$NON-NLS-1$
430: return false;
431: }
432: } else {
433: return false;
434: }
435: }
436: }
437:
438: //javadoc:3.7
439: if (this .location.getRef() != null) {
440: if (!this .location.getRef()
441: .equals(cs.location.getRef())) {
442: return false;
443: }
444: }
445: // ok, every check was made, and they all were successful.
446: // it's ok to return true.
447: } // if this.location != null
448:
449: // javadoc: a note about CodeSource with null location and null Certs
450: // is applicable here
451: return true;
452: }
453:
454: /**
455: * Answers a string containing a concise, human-readable description of the
456: * receiver.
457: *
458: *
459: * @return a printable representation for the receiver.
460: */
461: public String toString() {
462: StringBuilder buf = new StringBuilder();
463: buf.append("CodeSource, url="); //$NON-NLS-1$
464: buf.append(location == null ? "<null>" : location.toString()); //$NON-NLS-1$
465:
466: if (certs == null) {
467: buf.append(", <no certificates>"); //$NON-NLS-1$
468: } else {
469: buf.append("\nCertificates [\n"); //$NON-NLS-1$
470: for (int i = 0; i < certs.length; i++) {
471: buf.append(i + 1)
472: .append(") ").append(certs[i]).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
473: }
474: buf.append("]\n"); //$NON-NLS-1$
475: }
476: if (signers != null) {
477: buf.append("\nCodeSigners [\n"); //$NON-NLS-1$
478: for (int i = 0; i < signers.length; i++) {
479: buf.append(i + 1)
480: .append(") ").append(signers[i]).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
481: }
482: buf.append("]\n"); //$NON-NLS-1$
483: }
484: return buf.toString();
485: }
486:
487: private void writeObject(ObjectOutputStream oos) throws IOException {
488:
489: oos.defaultWriteObject();
490:
491: if (certs == null || certs.length == 0) {
492: oos.writeInt(0);
493: } else {
494: oos.writeInt(certs.length);
495: for (int i = 0; i < certs.length; i++) {
496: try {
497: oos.writeUTF(certs[i].getType());
498: byte[] data = certs[i].getEncoded();
499: // hope there are no certificates with 'data==null'
500: oos.writeInt(data.length);
501: oos.write(data);
502: } catch (CertificateEncodingException ex) {
503: throw (IOException) new IOException(Messages
504: .getString("security.18")).initCause(ex); //$NON-NLS-1$
505: }
506: }
507: }
508: if (signers != null && signers.length != 0) {
509: oos.writeObject(signers);
510: }
511: }
512:
513: private void readObject(ObjectInputStream ois) throws IOException,
514: ClassNotFoundException {
515:
516: ois.defaultReadObject();
517:
518: int certsCount = ois.readInt();
519: certs = null;
520: if (certsCount != 0) {
521: certs = new Certificate[certsCount];
522: for (int i = 0; i < certsCount; i++) {
523: String type = ois.readUTF();
524: CertificateFactory factory;
525: try {
526: factory = CertificateFactory.getInstance(type);
527: } catch (CertificateException ex) {
528: throw new ClassNotFoundException(Messages
529: .getString("security.19", type), //$NON-NLS-1$
530: ex);
531: }
532: int dataLen = ois.readInt();
533: byte[] data = new byte[dataLen];
534: ois.readFully(data);
535: ByteArrayInputStream bais = new ByteArrayInputStream(
536: data);
537: try {
538: certs[i] = factory.generateCertificate(bais);
539: } catch (CertificateException ex) {
540: throw (IOException) new IOException(Messages
541: .getString("security.1A")).initCause(ex); //$NON-NLS-1$
542: }
543: }
544: }
545: try {
546: signers = (CodeSigner[]) ois.readObject();
547: } catch (OptionalDataException ex) {
548: if (!ex.eof) {
549: throw ex;
550: }
551: // no signers (ex.eof==true <= no data left) is allowed
552: }
553: }
554: }
|