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