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