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