1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.  Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27package java.util.jar;
28
29import java.io.*;
30import java.net.URL;
31import java.util.*;
32import java.security.*;
33import java.security.cert.CertificateException;
34import java.util.zip.ZipEntry;
35
36import sun.security.util.ManifestDigester;
37import sun.security.util.ManifestEntryVerifier;
38import sun.security.util.SignatureFileVerifier;
39import sun.security.util.Debug;
40
41/**
42 *
43 * @author      Roland Schemers
44 */
45class JarVerifier {
46
47    /* Are we debugging ? */
48    static final Debug debug = Debug.getInstance("jar");
49
50    /* a table mapping names to code signers, for jar entries that have
51       had their actual hashes verified */
52    private Hashtable verifiedSigners;
53
54    /* a table mapping names to code signers, for jar entries that have
55       passed the .SF/.DSA/.EC -> MANIFEST check */
56    private Hashtable sigFileSigners;
57
58    /* a hash table to hold .SF bytes */
59    private Hashtable sigFileData;
60
61    /** "queue" of pending PKCS7 blocks that we couldn't parse
62     *  until we parsed the .SF file */
63    private ArrayList pendingBlocks;
64
65    /* cache of CodeSigner objects */
66    private ArrayList signerCache;
67
68    /* Are we parsing a block? */
69    private boolean parsingBlockOrSF = false;
70
71    /* Are we done parsing META-INF entries? */
72    private boolean parsingMeta = true;
73
74    /* Are there are files to verify? */
75    private boolean anyToVerify = true;
76
77    /* The output stream to use when keeping track of files we are interested
78       in */
79    private ByteArrayOutputStream baos;
80
81    /** The ManifestDigester object */
82    private volatile ManifestDigester manDig;
83
84    /** the bytes for the manDig object */
85    byte manifestRawBytes[] = null;
86
87    /** controls eager signature validation */
88    boolean eagerValidation;
89
90    /** makes code source singleton instances unique to us */
91    private Object csdomain = new Object();
92
93    /** collect -DIGEST-MANIFEST values for blacklist */
94    private List manifestDigests;
95
96    public JarVerifier(byte rawBytes[]) {
97        manifestRawBytes = rawBytes;
98        sigFileSigners = new Hashtable();
99        verifiedSigners = new Hashtable();
100        sigFileData = new Hashtable(11);
101        pendingBlocks = new ArrayList();
102        baos = new ByteArrayOutputStream();
103        manifestDigests = new ArrayList();
104    }
105
106    /**
107     * This method scans to see which entry we're parsing and
108     * keeps various state information depending on what type of
109     * file is being parsed.
110     */
111    public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
112        throws IOException
113    {
114        if (je == null)
115            return;
116
117        if (debug != null) {
118            debug.println("beginEntry "+je.getName());
119        }
120
121        String name = je.getName();
122
123        /*
124         * Assumptions:
125         * 1. The manifest should be the first entry in the META-INF directory.
126         * 2. The .SF/.DSA/.EC files follow the manifest, before any normal entries
127         * 3. Any of the following will throw a SecurityException:
128         *    a. digest mismatch between a manifest section and
129         *       the SF section.
130         *    b. digest mismatch between the actual jar entry and the manifest
131         */
132
133        if (parsingMeta) {
134            String uname = name.toUpperCase(Locale.ENGLISH);
135            if ((uname.startsWith("META-INF/") ||
136                 uname.startsWith("/META-INF/"))) {
137
138                if (je.isDirectory()) {
139                    mev.setEntry(null, je);
140                    return;
141                }
142
143                if (SignatureFileVerifier.isBlockOrSF(uname)) {
144                    /* We parse only DSA, RSA or EC PKCS7 blocks. */
145                    parsingBlockOrSF = true;
146                    baos.reset();
147                    mev.setEntry(null, je);
148                }
149                return;
150            }
151        }
152
153        if (parsingMeta) {
154            doneWithMeta();
155        }
156
157        if (je.isDirectory()) {
158            mev.setEntry(null, je);
159            return;
160        }
161
162        // be liberal in what you accept. If the name starts with ./, remove
163        // it as we internally canonicalize it with out the ./.
164        if (name.startsWith("./"))
165            name = name.substring(2);
166
167        // be liberal in what you accept. If the name starts with /, remove
168        // it as we internally canonicalize it with out the /.
169        if (name.startsWith("/"))
170            name = name.substring(1);
171
172        // only set the jev object for entries that have a signature
173        if (sigFileSigners.get(name) != null) {
174            mev.setEntry(name, je);
175            return;
176        }
177
178        // don't compute the digest for this entry
179        mev.setEntry(null, je);
180
181        return;
182    }
183
184    /**
185     * update a single byte.
186     */
187
188    public void update(int b, ManifestEntryVerifier mev)
189        throws IOException
190    {
191        if (b != -1) {
192            if (parsingBlockOrSF) {
193                baos.write(b);
194            } else {
195                mev.update((byte)b);
196            }
197        } else {
198            processEntry(mev);
199        }
200    }
201
202    /**
203     * update an array of bytes.
204     */
205
206    public void update(int n, byte[] b, int off, int len,
207                       ManifestEntryVerifier mev)
208        throws IOException
209    {
210        if (n != -1) {
211            if (parsingBlockOrSF) {
212                baos.write(b, off, n);
213            } else {
214                mev.update(b, off, n);
215            }
216        } else {
217            processEntry(mev);
218        }
219    }
220
221    /**
222     * called when we reach the end of entry in one of the read() methods.
223     */
224    private void processEntry(ManifestEntryVerifier mev)
225        throws IOException
226    {
227        if (!parsingBlockOrSF) {
228            JarEntry je = mev.getEntry();
229            if ((je != null) && (je.signers == null)) {
230                je.signers = mev.verify(verifiedSigners, sigFileSigners);
231                je.certs = mapSignersToCertArray(je.signers);
232            }
233        } else {
234
235            try {
236                parsingBlockOrSF = false;
237
238                if (debug != null) {
239                    debug.println("processEntry: processing block");
240                }
241
242                String uname = mev.getEntry().getName()
243                                             .toUpperCase(Locale.ENGLISH);
244
245                if (uname.endsWith(".SF")) {
246                    String key = uname.substring(0, uname.length()-3);
247                    byte bytes[] = baos.toByteArray();
248                    // add to sigFileData in case future blocks need it
249                    sigFileData.put(key, bytes);
250                    // check pending blocks, we can now process
251                    // anyone waiting for this .SF file
252                    Iterator it = pendingBlocks.iterator();
253                    while (it.hasNext()) {
254                        SignatureFileVerifier sfv =
255                            (SignatureFileVerifier) it.next();
256                        if (sfv.needSignatureFile(key)) {
257                            if (debug != null) {
258                                debug.println(
259                                 "processEntry: processing pending block");
260                            }
261
262                            sfv.setSignatureFile(bytes);
263                            sfv.process(sigFileSigners, manifestDigests);
264                        }
265                    }
266                    return;
267                }
268
269                // now we are parsing a signature block file
270
271                String key = uname.substring(0, uname.lastIndexOf("."));
272
273                if (signerCache == null)
274                    signerCache = new ArrayList();
275
276                if (manDig == null) {
277                    synchronized(manifestRawBytes) {
278                        if (manDig == null) {
279                            manDig = new ManifestDigester(manifestRawBytes);
280                            manifestRawBytes = null;
281                        }
282                    }
283                }
284
285                SignatureFileVerifier sfv =
286                  new SignatureFileVerifier(signerCache,
287                                            manDig, uname, baos.toByteArray());
288
289                if (sfv.needSignatureFileBytes()) {
290                    // see if we have already parsed an external .SF file
291                    byte[] bytes = (byte[]) sigFileData.get(key);
292
293                    if (bytes == null) {
294                        // put this block on queue for later processing
295                        // since we don't have the .SF bytes yet
296                        // (uname, block);
297                        if (debug != null) {
298                            debug.println("adding pending block");
299                        }
300                        pendingBlocks.add(sfv);
301                        return;
302                    } else {
303                        sfv.setSignatureFile(bytes);
304                    }
305                }
306                sfv.process(sigFileSigners, manifestDigests);
307
308            } catch (IOException ioe) {
309                // e.g. sun.security.pkcs.ParsingException
310                if (debug != null) debug.println("processEntry caught: "+ioe);
311                // ignore and treat as unsigned
312            } catch (SignatureException se) {
313                if (debug != null) debug.println("processEntry caught: "+se);
314                // ignore and treat as unsigned
315            } catch (NoSuchAlgorithmException nsae) {
316                if (debug != null) debug.println("processEntry caught: "+nsae);
317                // ignore and treat as unsigned
318            } catch (CertificateException ce) {
319                if (debug != null) debug.println("processEntry caught: "+ce);
320                // ignore and treat as unsigned
321            }
322        }
323    }
324
325    /**
326     * Return an array of java.security.cert.Certificate objects for
327     * the given file in the jar.
328     * @deprecated Deprecated.
329     */
330    @Deprecated // Android-changed added "Deprecated."
331    public java.security.cert.Certificate[] getCerts(String name)
332    {
333        return mapSignersToCertArray(getCodeSigners(name));
334    }
335
336    public java.security.cert.Certificate[] getCerts(JarFile jar, JarEntry entry)
337    {
338        return mapSignersToCertArray(getCodeSigners(jar, entry));
339    }
340
341    /**
342     * return an array of CodeSigner objects for
343     * the given file in the jar. this array is not cloned.
344     *
345     */
346    public CodeSigner[] getCodeSigners(String name)
347    {
348        return (CodeSigner[])verifiedSigners.get(name);
349    }
350
351    public CodeSigner[] getCodeSigners(JarFile jar, JarEntry entry)
352    {
353        String name = entry.getName();
354        if (eagerValidation && sigFileSigners.get(name) != null) {
355            /*
356             * Force a read of the entry data to generate the
357             * verification hash.
358             */
359            try {
360                InputStream s = jar.getInputStream(entry);
361                byte[] buffer = new byte[1024];
362                int n = buffer.length;
363                while (n != -1) {
364                    n = s.read(buffer, 0, buffer.length);
365                }
366                s.close();
367            } catch (IOException e) {
368            }
369        }
370        return getCodeSigners(name);
371    }
372
373    /*
374     * Convert an array of signers into an array of concatenated certificate
375     * arrays.
376     */
377    private static java.security.cert.Certificate[] mapSignersToCertArray(
378        CodeSigner[] signers) {
379
380        if (signers != null) {
381            ArrayList certChains = new ArrayList();
382            for (int i = 0; i < signers.length; i++) {
383                certChains.addAll(
384                    signers[i].getSignerCertPath().getCertificates());
385            }
386
387            // Convert into a Certificate[]
388            return (java.security.cert.Certificate[])
389                certChains.toArray(
390                    new java.security.cert.Certificate[certChains.size()]);
391        }
392        return null;
393    }
394
395    /**
396     * returns true if there no files to verify.
397     * should only be called after all the META-INF entries
398     * have been processed.
399     */
400    boolean nothingToVerify()
401    {
402        return (anyToVerify == false);
403    }
404
405    /**
406     * called to let us know we have processed all the
407     * META-INF entries, and if we re-read one of them, don't
408     * re-process it. Also gets rid of any data structures
409     * we needed when parsing META-INF entries.
410     */
411    void doneWithMeta()
412    {
413        parsingMeta = false;
414        anyToVerify = !sigFileSigners.isEmpty();
415        baos = null;
416        sigFileData = null;
417        pendingBlocks = null;
418        signerCache = null;
419        manDig = null;
420        // MANIFEST.MF is always treated as signed and verified,
421        // move its signers from sigFileSigners to verifiedSigners.
422        if (sigFileSigners.containsKey(JarFile.MANIFEST_NAME)) {
423            verifiedSigners.put(JarFile.MANIFEST_NAME,
424                    sigFileSigners.remove(JarFile.MANIFEST_NAME));
425        }
426    }
427
428    static class VerifierStream extends java.io.InputStream {
429
430        private InputStream is;
431        private JarVerifier jv;
432        private ManifestEntryVerifier mev;
433        private long numLeft;
434
435        VerifierStream(Manifest man,
436                       JarEntry je,
437                       InputStream is,
438                       JarVerifier jv) throws IOException
439        {
440            // Android changed : Added to make sure inputs are not null. This allows to
441            // use is == null to detect closed verifier streams.
442            if (is == null) {
443                throw new NullPointerException("is == null");
444            }
445            this.is = is;
446            this.jv = jv;
447            this.mev = new ManifestEntryVerifier(man);
448            this.jv.beginEntry(je, mev);
449            this.numLeft = je.getSize();
450            if (this.numLeft == 0)
451                this.jv.update(-1, this.mev);
452        }
453
454        public int read() throws IOException
455        {
456            // Android added.
457            if (is == null) {
458                throw new IOException("stream closed");
459            }
460
461            if (numLeft > 0) {
462                int b = is.read();
463                jv.update(b, mev);
464                numLeft--;
465                if (numLeft == 0)
466                    jv.update(-1, mev);
467                return b;
468            } else {
469                return -1;
470            }
471        }
472
473        public int read(byte b[], int off, int len) throws IOException {
474            // Android added.
475            if (is == null) {
476                throw new IOException("stream closed");
477            }
478
479            if ((numLeft > 0) && (numLeft < len)) {
480                len = (int)numLeft;
481            }
482
483            if (numLeft > 0) {
484                int n = is.read(b, off, len);
485                jv.update(n, b, off, len, mev);
486                numLeft -= n;
487                if (numLeft == 0)
488                    jv.update(-1, b, off, len, mev);
489                return n;
490            } else {
491                return -1;
492            }
493        }
494
495        public void close()
496            throws IOException
497        {
498            if (is != null)
499                is.close();
500            is = null;
501            mev = null;
502            jv = null;
503        }
504
505        public int available() throws IOException {
506            // Android added.
507            if (is == null) {
508                throw new IOException("stream closed");
509            }
510
511            return is.available();
512        }
513
514    }
515
516    // Extended JavaUtilJarAccess CodeSource API Support
517
518    private Map urlToCodeSourceMap = new HashMap();
519    private Map signerToCodeSource = new HashMap();
520    private URL lastURL;
521    private Map lastURLMap;
522
523    /*
524     * Create a unique mapping from codeSigner cache entries to CodeSource.
525     * In theory, multiple URLs origins could map to a single locally cached
526     * and shared JAR file although in practice there will be a single URL in use.
527     */
528    private synchronized CodeSource mapSignersToCodeSource(URL url, CodeSigner[] signers) {
529        Map map;
530        if (url == lastURL) {
531            map = lastURLMap;
532        } else {
533            map = (Map) urlToCodeSourceMap.get(url);
534            if (map == null) {
535                map = new HashMap();
536                urlToCodeSourceMap.put(url, map);
537            }
538            lastURLMap = map;
539            lastURL = url;
540        }
541        CodeSource cs = (CodeSource) map.get(signers);
542        if (cs == null) {
543            cs = new VerifierCodeSource(csdomain, url, signers);
544            signerToCodeSource.put(signers, cs);
545        }
546        return cs;
547    }
548
549    private CodeSource[] mapSignersToCodeSources(URL url, List signers, boolean unsigned) {
550        List sources = new ArrayList();
551
552        for (int i = 0; i < signers.size(); i++) {
553            sources.add(mapSignersToCodeSource(url, (CodeSigner[]) signers.get(i)));
554        }
555        if (unsigned) {
556            sources.add(mapSignersToCodeSource(url, null));
557        }
558        return (CodeSource[]) sources.toArray(new CodeSource[sources.size()]);
559    }
560    private CodeSigner[] emptySigner = new CodeSigner[0];
561
562    /*
563     * Match CodeSource to a CodeSigner[] in the signer cache.
564     */
565    private CodeSigner[] findMatchingSigners(CodeSource cs) {
566        if (cs instanceof VerifierCodeSource) {
567            VerifierCodeSource vcs = (VerifierCodeSource) cs;
568            if (vcs.isSameDomain(csdomain)) {
569                return ((VerifierCodeSource) cs).getPrivateSigners();
570            }
571        }
572
573        /*
574         * In practice signers should always be optimized above
575         * but this handles a CodeSource of any type, just in case.
576         */
577        CodeSource[] sources = mapSignersToCodeSources(cs.getLocation(), getJarCodeSigners(), true);
578        List sourceList = new ArrayList();
579        for (int i = 0; i < sources.length; i++) {
580            sourceList.add(sources[i]);
581        }
582        int j = sourceList.indexOf(cs);
583        if (j != -1) {
584            CodeSigner[] match;
585            match = ((VerifierCodeSource) sourceList.get(j)).getPrivateSigners();
586            if (match == null) {
587                match = emptySigner;
588            }
589            return match;
590        }
591        return null;
592    }
593
594    /*
595     * Instances of this class hold uncopied references to internal
596     * signing data that can be compared by object reference identity.
597     */
598    private static class VerifierCodeSource extends CodeSource {
599
600        URL vlocation;
601        CodeSigner[] vsigners;
602        java.security.cert.Certificate[] vcerts;
603        Object csdomain;
604
605        VerifierCodeSource(Object csdomain, URL location, CodeSigner[] signers) {
606            super(location, signers);
607            this.csdomain = csdomain;
608            vlocation = location;
609            vsigners = signers; // from signerCache
610        }
611
612        VerifierCodeSource(Object csdomain, URL location, java.security.cert.Certificate[] certs) {
613            super(location, certs);
614            this.csdomain = csdomain;
615            vlocation = location;
616            vcerts = certs; // from signerCache
617        }
618
619        /*
620         * All VerifierCodeSource instances are constructed based on
621         * singleton signerCache or signerCacheCert entries for each unique signer.
622         * No CodeSigner<->Certificate[] conversion is required.
623         * We use these assumptions to optimize equality comparisons.
624         */
625        public boolean equals(Object obj) {
626            if (obj == this) {
627                return true;
628            }
629            if (obj instanceof VerifierCodeSource) {
630                VerifierCodeSource that = (VerifierCodeSource) obj;
631
632                /*
633                 * Only compare against other per-signer singletons constructed
634                 * on behalf of the same JarFile instance. Otherwise, compare
635                 * things the slower way.
636                 */
637                if (isSameDomain(that.csdomain)) {
638                    if (that.vsigners != this.vsigners
639                            || that.vcerts != this.vcerts) {
640                        return false;
641                    }
642                    if (that.vlocation != null) {
643                        return that.vlocation.equals(this.vlocation);
644                    } else if (this.vlocation != null) {
645                        return this.vlocation.equals(that.vlocation);
646                    } else { // both null
647                        return true;
648                    }
649                }
650            }
651            return super.equals(obj);
652        }
653
654        boolean isSameDomain(Object csdomain) {
655            return this.csdomain == csdomain;
656        }
657
658        private CodeSigner[] getPrivateSigners() {
659            return vsigners;
660        }
661
662        private java.security.cert.Certificate[] getPrivateCertificates() {
663            return vcerts;
664        }
665    }
666    private Map signerMap;
667
668    private synchronized Map signerMap() {
669        if (signerMap == null) {
670            /*
671             * Snapshot signer state so it doesn't change on us. We care
672             * only about the asserted signatures. Verification of
673             * signature validity happens via the JarEntry apis.
674             */
675            signerMap = new HashMap(verifiedSigners.size() + sigFileSigners.size());
676            signerMap.putAll(verifiedSigners);
677            signerMap.putAll(sigFileSigners);
678        }
679        return signerMap;
680    }
681
682    public synchronized Enumeration<String> entryNames(JarFile jar, final CodeSource[] cs) {
683        final Map map = signerMap();
684        final Iterator itor = map.entrySet().iterator();
685        boolean matchUnsigned = false;
686
687        /*
688         * Grab a single copy of the CodeSigner arrays. Check
689         * to see if we can optimize CodeSigner equality test.
690         */
691        List req = new ArrayList(cs.length);
692        for (int i = 0; i < cs.length; i++) {
693            CodeSigner[] match = findMatchingSigners(cs[i]);
694            if (match != null) {
695                if (match.length > 0) {
696                    req.add(match);
697                } else {
698                    matchUnsigned = true;
699                }
700            }
701        }
702
703        final List signersReq = req;
704        final Enumeration enum2 = (matchUnsigned) ? unsignedEntryNames(jar) : emptyEnumeration;
705
706        return new Enumeration<String>() {
707
708            String name;
709
710            public boolean hasMoreElements() {
711                if (name != null) {
712                    return true;
713                }
714
715                while (itor.hasNext()) {
716                    Map.Entry e = (Map.Entry) itor.next();
717                    if (signersReq.contains((CodeSigner[]) e.getValue())) {
718                        name = (String) e.getKey();
719                        return true;
720                    }
721                }
722                while (enum2.hasMoreElements()) {
723                    name = (String) enum2.nextElement();
724                    return true;
725                }
726                return false;
727            }
728
729            public String nextElement() {
730                if (hasMoreElements()) {
731                    String value = name;
732                    name = null;
733                    return value;
734                }
735                throw new NoSuchElementException();
736            }
737        };
738    }
739
740    /*
741     * Like entries() but screens out internal JAR mechanism entries
742     * and includes signed entries with no ZIP data.
743     */
744    public Enumeration<JarEntry> entries2(final JarFile jar, Enumeration e) {
745        final Map map = new HashMap();
746        map.putAll(signerMap());
747        final Enumeration enum_ = e;
748        return new Enumeration<JarEntry>() {
749
750            Enumeration signers = null;
751            JarEntry entry;
752
753            public boolean hasMoreElements() {
754                if (entry != null) {
755                    return true;
756                }
757                while (enum_.hasMoreElements()) {
758                    ZipEntry ze = (ZipEntry) enum_.nextElement();
759                    if (JarVerifier.isSigningRelated(ze.getName())) {
760                        continue;
761                    }
762                    entry = jar.newEntry(ze);
763                    return true;
764                }
765                if (signers == null) {
766                    signers = Collections.enumeration(map.keySet());
767                }
768                while (signers.hasMoreElements()) {
769                    String name = (String) signers.nextElement();
770                    entry = jar.newEntry(new ZipEntry(name));
771                    return true;
772                }
773
774                // Any map entries left?
775                return false;
776            }
777
778            public JarEntry nextElement() {
779                if (hasMoreElements()) {
780                    JarEntry je = entry;
781                    map.remove(je.getName());
782                    entry = null;
783                    return je;
784                }
785                throw new NoSuchElementException();
786            }
787        };
788    }
789    private Enumeration emptyEnumeration = new Enumeration<String>() {
790
791        public boolean hasMoreElements() {
792            return false;
793        }
794
795        public String nextElement() {
796            throw new NoSuchElementException();
797        }
798    };
799
800    // true if file is part of the signature mechanism itself
801    static boolean isSigningRelated(String name) {
802        name = name.toUpperCase(Locale.ENGLISH);
803        if (!name.startsWith("META-INF/")) {
804            return false;
805        }
806        name = name.substring(9);
807        if (name.indexOf('/') != -1) {
808            return false;
809        }
810        if (name.endsWith(".DSA")
811                || name.endsWith(".RSA")
812                || name.endsWith(".SF")
813                || name.endsWith(".EC")
814                || name.startsWith("SIG-")
815                || name.equals("MANIFEST.MF")) {
816            return true;
817        }
818        return false;
819    }
820
821    private Enumeration<String> unsignedEntryNames(JarFile jar) {
822        final Map map = signerMap();
823        final Enumeration entries = jar.entries();
824        return new Enumeration<String>() {
825
826            String name;
827
828            /*
829             * Grab entries from ZIP directory but screen out
830             * metadata.
831             */
832            public boolean hasMoreElements() {
833                if (name != null) {
834                    return true;
835                }
836                while (entries.hasMoreElements()) {
837                    String value;
838                    ZipEntry e = (ZipEntry) entries.nextElement();
839                    value = e.getName();
840                    if (e.isDirectory() || isSigningRelated(value)) {
841                        continue;
842                    }
843                    if (map.get(value) == null) {
844                        name = value;
845                        return true;
846                    }
847                }
848                return false;
849            }
850
851            public String nextElement() {
852                if (hasMoreElements()) {
853                    String value = name;
854                    name = null;
855                    return value;
856                }
857                throw new NoSuchElementException();
858            }
859        };
860    }
861    private List jarCodeSigners;
862
863    private synchronized List getJarCodeSigners() {
864        CodeSigner[] signers;
865        if (jarCodeSigners == null) {
866            HashSet set = new HashSet();
867            set.addAll(signerMap().values());
868            jarCodeSigners = new ArrayList();
869            jarCodeSigners.addAll(set);
870        }
871        return jarCodeSigners;
872    }
873
874    public synchronized CodeSource[] getCodeSources(JarFile jar, URL url) {
875        boolean hasUnsigned = unsignedEntryNames(jar).hasMoreElements();
876
877        return mapSignersToCodeSources(url, getJarCodeSigners(), hasUnsigned);
878    }
879
880    public CodeSource getCodeSource(URL url, String name) {
881        CodeSigner[] signers;
882
883        signers = (CodeSigner[]) signerMap().get(name);
884        return mapSignersToCodeSource(url, signers);
885    }
886
887    public CodeSource getCodeSource(URL url, JarFile jar, JarEntry je) {
888        CodeSigner[] signers;
889
890        return mapSignersToCodeSource(url, getCodeSigners(jar, je));
891    }
892
893    public void setEagerValidation(boolean eager) {
894        eagerValidation = eager;
895    }
896
897    public synchronized List getManifestDigests() {
898        return Collections.unmodifiableList(manifestDigests);
899    }
900
901    static CodeSource getUnsignedCS(URL url) {
902        return new VerifierCodeSource(null, url, (java.security.cert.Certificate[]) null);
903    }
904}
905