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