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