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