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