001    /* JarFile.java - Representation of a jar file
002       Copyright (C) 2000, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.util.jar;
040    
041    import gnu.java.io.Base64InputStream;
042    import gnu.java.security.OID;
043    import gnu.java.security.pkcs.PKCS7SignedData;
044    import gnu.java.security.pkcs.SignerInfo;
045    import gnu.java.security.provider.Gnu;
046    
047    import java.io.ByteArrayOutputStream;
048    import java.io.File;
049    import java.io.FileNotFoundException;
050    import java.io.FilterInputStream;
051    import java.io.IOException;
052    import java.io.InputStream;
053    import java.security.InvalidKeyException;
054    import java.security.MessageDigest;
055    import java.security.NoSuchAlgorithmException;
056    import java.security.Signature;
057    import java.security.SignatureException;
058    import java.security.cert.CRLException;
059    import java.security.cert.Certificate;
060    import java.security.cert.CertificateException;
061    import java.security.cert.X509Certificate;
062    import java.util.Arrays;
063    import java.util.Enumeration;
064    import java.util.HashMap;
065    import java.util.HashSet;
066    import java.util.Iterator;
067    import java.util.LinkedList;
068    import java.util.List;
069    import java.util.Map;
070    import java.util.Set;
071    import java.util.regex.Matcher;
072    import java.util.regex.Pattern;
073    import java.util.zip.ZipEntry;
074    import java.util.zip.ZipException;
075    import java.util.zip.ZipFile;
076    
077    /**
078     * Representation of a jar file.
079     * <p>
080     * Note that this class is not a subclass of java.io.File but a subclass of
081     * java.util.zip.ZipFile and you can only read JarFiles with it (although
082     * there are constructors that take a File object).
083     *
084     * @since 1.2
085     * @author Mark Wielaard (mark@klomp.org)
086     * @author Casey Marshall (csm@gnu.org) wrote the certificate and entry
087     *  verification code.
088     */
089    public class JarFile extends ZipFile
090    {
091      // Fields
092    
093      /** The name of the manifest entry: META-INF/MANIFEST.MF */
094      public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
095    
096      /** The META-INF directory entry. */
097      private static final String META_INF = "META-INF/";
098    
099      /** The suffix for PKCS7 DSA signature entries. */
100      private static final String PKCS7_DSA_SUFFIX = ".DSA";
101    
102      /** The suffix for PKCS7 RSA signature entries. */
103      private static final String PKCS7_RSA_SUFFIX = ".RSA";
104    
105      /** The suffix for digest attributes. */
106      private static final String DIGEST_KEY_SUFFIX = "-Digest";
107    
108      /** The suffix for signature files. */
109      private static final String SF_SUFFIX = ".SF";
110    
111      /**
112       * The security provider to use for signature verification.
113       * We need a known fallback to be able to read any signed jar file
114       * (which might contain the user selected security provider).
115       * This is package-private to avoid accessor methods for inner classes.
116       */
117      static final Gnu provider = new Gnu();
118    
119      // Signature OIDs.
120      private static final OID MD2_OID = new OID("1.2.840.113549.2.2");
121      private static final OID MD4_OID = new OID("1.2.840.113549.2.4");
122      private static final OID MD5_OID = new OID("1.2.840.113549.2.5");
123      private static final OID SHA1_OID = new OID("1.3.14.3.2.26");
124      private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1");
125      private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1");
126    
127      /**
128       * The manifest of this file, if any, otherwise null.
129       * Read when first needed.
130       */
131      private Manifest manifest;
132    
133      /** Whether to verify the manifest and all entries. */
134      boolean verify;
135    
136      /** Whether the has already been loaded. */
137      private boolean manifestRead = false;
138    
139      /** Whether the signature files have been loaded. */
140      boolean signaturesRead = false;
141    
142      /**
143       * A map between entry names and booleans, signaling whether or
144       * not that entry has been verified.
145       * Only be accessed with lock on this JarFile*/
146      HashMap verified = new HashMap();
147    
148      /**
149       * A mapping from entry name to certificates, if any.
150       * Only accessed with lock on this JarFile.
151       */
152      HashMap entryCerts;
153    
154      /**
155       * A {@link Map} of message digest algorithm names to their implementation.
156       * Used to reduce object (algorithm implementation) instantiation.
157       */
158      private HashMap digestAlgorithms = new HashMap();
159    
160      static boolean DEBUG = false;
161      static void debug(Object msg)
162      {
163        System.err.print(JarFile.class.getName());
164        System.err.print(" >>> ");
165        System.err.println(msg);
166      }
167    
168      // Constructors
169    
170      /**
171       * Creates a new JarFile. All jar entries are verified (when a Manifest file
172       * for this JarFile exists). You need to actually open and read the complete
173       * jar entry (with <code>getInputStream()</code>) to check its signature.
174       *
175       * @param fileName the name of the file to open
176       * @exception FileNotFoundException if the fileName cannot be found
177       * @exception IOException if another IO exception occurs while reading
178       */
179      public JarFile(String fileName) throws FileNotFoundException, IOException
180      {
181        this(fileName, true);
182      }
183    
184      /**
185       * Creates a new JarFile. If verify is true then all jar entries are
186       * verified (when a Manifest file for this JarFile exists). You need to
187       * actually open and read the complete jar entry
188       * (with <code>getInputStream()</code>) to check its signature.
189       *
190       * @param fileName the name of the file to open
191       * @param verify checks manifest and entries when true and a manifest
192       * exists, when false no checks are made
193       * @exception FileNotFoundException if the fileName cannot be found
194       * @exception IOException if another IO exception occurs while reading
195       */
196      public JarFile(String fileName, boolean verify) throws
197        FileNotFoundException, IOException
198      {
199        super(fileName);
200        if (verify)
201          {
202            manifest = readManifest();
203            verify();
204          }
205      }
206    
207      /**
208       * Creates a new JarFile. All jar entries are verified (when a Manifest file
209       * for this JarFile exists). You need to actually open and read the complete
210       * jar entry (with <code>getInputStream()</code>) to check its signature.
211       *
212       * @param file the file to open as a jar file
213       * @exception FileNotFoundException if the file does not exits
214       * @exception IOException if another IO exception occurs while reading
215       */
216      public JarFile(File file) throws FileNotFoundException, IOException
217      {
218        this(file, true);
219      }
220    
221      /**
222       * Creates a new JarFile. If verify is true then all jar entries are
223       * verified (when a Manifest file for this JarFile exists). You need to
224       * actually open and read the complete jar entry
225       * (with <code>getInputStream()</code>) to check its signature.
226       *
227       * @param file the file to open to open as a jar file
228       * @param verify checks manifest and entries when true and a manifest
229       * exists, when false no checks are made
230       * @exception FileNotFoundException if file does not exist
231       * @exception IOException if another IO exception occurs while reading
232       */
233      public JarFile(File file, boolean verify) throws FileNotFoundException,
234        IOException
235      {
236        super(file);
237        if (verify)
238          {
239            manifest = readManifest();
240            verify();
241          }
242      }
243    
244      /**
245       * Creates a new JarFile with the indicated mode. If verify is true then
246       * all jar entries are verified (when a Manifest file for this JarFile
247       * exists). You need to actually open and read the complete jar entry
248       * (with <code>getInputStream()</code>) to check its signature.
249       * manifest and if the manifest exists and verify is true verfies it.
250       *
251       * @param file the file to open to open as a jar file
252       * @param verify checks manifest and entries when true and a manifest
253       * exists, when false no checks are made
254       * @param mode either ZipFile.OPEN_READ or
255       *             (ZipFile.OPEN_READ | ZipFile.OPEN_DELETE)
256       * @exception FileNotFoundException if the file does not exist
257       * @exception IOException if another IO exception occurs while reading
258       * @exception IllegalArgumentException when given an illegal mode
259       *
260       * @since 1.3
261       */
262      public JarFile(File file, boolean verify, int mode) throws
263        FileNotFoundException, IOException, IllegalArgumentException
264      {
265        super(file, mode);
266        if (verify)
267          {
268            manifest = readManifest();
269            verify();
270          }
271      }
272    
273      // Methods
274    
275      /**
276       * XXX - should verify the manifest file
277       */
278      private void verify()
279      {
280        // only check if manifest is not null
281        if (manifest == null)
282          {
283            verify = false;
284            return;
285          }
286    
287        verify = true;
288        // XXX - verify manifest
289      }
290    
291      /**
292       * Parses and returns the manifest if it exists, otherwise returns null.
293       */
294      private Manifest readManifest()
295      {
296        try
297          {
298            ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
299            if (manEntry != null)
300              {
301                InputStream in = super.getInputStream(manEntry);
302                manifestRead = true;
303                return new Manifest(in);
304              }
305            else
306              {
307                manifestRead = true;
308                return null;
309              }
310          }
311        catch (IOException ioe)
312          {
313            manifestRead = true;
314            return null;
315          }
316      }
317    
318      /**
319       * Returns a enumeration of all the entries in the JarFile.
320       * Note that also the Jar META-INF entries are returned.
321       *
322       * @exception IllegalStateException when the JarFile is already closed
323       */
324      public Enumeration<JarEntry> entries() throws IllegalStateException
325      {
326        return new JarEnumeration(super.entries(), this);
327      }
328    
329      /**
330       * Wraps a given Zip Entries Enumeration. For every zip entry a
331       * JarEntry is created and the corresponding Attributes are looked up.
332       */
333      private static class JarEnumeration implements Enumeration<JarEntry>
334      {
335    
336        private final Enumeration<? extends ZipEntry> entries;
337        private final JarFile jarfile;
338    
339        JarEnumeration(Enumeration<? extends ZipEntry> e, JarFile f)
340        {
341          entries = e;
342          jarfile = f;
343        }
344    
345        public boolean hasMoreElements()
346        {
347          return entries.hasMoreElements();
348        }
349    
350        public JarEntry nextElement()
351        {
352          ZipEntry zip = (ZipEntry) entries.nextElement();
353          JarEntry jar = new JarEntry(zip);
354          Manifest manifest;
355          try
356            {
357              manifest = jarfile.getManifest();
358            }
359          catch (IOException ioe)
360            {
361              manifest = null;
362            }
363    
364          if (manifest != null)
365            {
366              jar.attr = manifest.getAttributes(jar.getName());
367            }
368    
369          synchronized(jarfile)
370            {
371              if (jarfile.verify && !jarfile.signaturesRead)
372                try
373                  {
374                    jarfile.readSignatures();
375                  }
376                catch (IOException ioe)
377                  {
378                    if (JarFile.DEBUG)
379                      {
380                        JarFile.debug(ioe);
381                        ioe.printStackTrace();
382                      }
383                    jarfile.signaturesRead = true; // fudge it.
384                  }
385            }
386          jar.jarfile = jarfile;
387          return jar;
388        }
389      }
390    
391      /**
392       * XXX
393       * It actually returns a JarEntry not a zipEntry
394       * @param name XXX
395       */
396      public synchronized ZipEntry getEntry(String name)
397      {
398        ZipEntry entry = super.getEntry(name);
399        if (entry != null)
400          {
401            JarEntry jarEntry = new JarEntry(entry);
402            Manifest manifest;
403            try
404              {
405                manifest = getManifest();
406              }
407            catch (IOException ioe)
408              {
409                manifest = null;
410              }
411    
412            if (manifest != null)
413              {
414                jarEntry.attr = manifest.getAttributes(name);
415              }
416    
417            if (verify && !signaturesRead)
418              try
419                {
420                  readSignatures();
421                }
422              catch (IOException ioe)
423                {
424                  if (DEBUG)
425                    {
426                      debug(ioe);
427                      ioe.printStackTrace();
428                    }
429                  signaturesRead = true;
430                }
431            jarEntry.jarfile = this;
432            return jarEntry;
433          }
434        return null;
435      }
436    
437      /**
438       * Returns an input stream for the given entry. If configured to
439       * verify entries, the input stream returned will verify them while
440       * the stream is read, but only on the first time.
441       *
442       * @param entry The entry to get the input stream for.
443       * @exception ZipException XXX
444       * @exception IOException XXX
445       */
446      public synchronized InputStream getInputStream(ZipEntry entry) throws
447        ZipException, IOException
448      {
449        // If we haven't verified the hash, do it now.
450        if (!verified.containsKey(entry.getName()) && verify)
451          {
452            if (DEBUG)
453              debug("reading and verifying " + entry);
454            return new EntryInputStream(entry, super.getInputStream(entry), this);
455          }
456        else
457          {
458            if (DEBUG)
459              debug("reading already verified entry " + entry);
460            if (verify && verified.get(entry.getName()) == Boolean.FALSE)
461              throw new ZipException("digest for " + entry + " is invalid");
462            return super.getInputStream(entry);
463          }
464      }
465    
466      /**
467       * Returns the JarEntry that belongs to the name if such an entry
468       * exists in the JarFile. Returns null otherwise
469       * Convenience method that just casts the result from <code>getEntry</code>
470       * to a JarEntry.
471       *
472       * @param name the jar entry name to look up
473       * @return the JarEntry if it exists, null otherwise
474       */
475      public JarEntry getJarEntry(String name)
476      {
477        return (JarEntry) getEntry(name);
478      }
479    
480      /**
481       * Returns the manifest for this JarFile or null when the JarFile does not
482       * contain a manifest file.
483       */
484      public synchronized Manifest getManifest() throws IOException
485      {
486        if (!manifestRead)
487          manifest = readManifest();
488    
489        return manifest;
490      }
491    
492      // Only called with lock on this JarFile.
493      // Package private for use in inner classes.
494      void readSignatures() throws IOException
495      {
496        Map pkcs7Dsa = new HashMap();
497        Map pkcs7Rsa = new HashMap();
498        Map sigFiles = new HashMap();
499    
500        // Phase 1: Read all signature files. These contain the user
501        // certificates as well as the signatures themselves.
502        for (Enumeration e = super.entries(); e.hasMoreElements(); )
503          {
504            ZipEntry ze = (ZipEntry) e.nextElement();
505            String name = ze.getName();
506            if (name.startsWith(META_INF))
507              {
508                String alias = name.substring(META_INF.length());
509                if (alias.lastIndexOf('.') >= 0)
510                  alias = alias.substring(0, alias.lastIndexOf('.'));
511    
512                if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX))
513                  {
514                    if (DEBUG)
515                      debug("reading PKCS7 info from " + name + ", alias=" + alias);
516                    PKCS7SignedData sig = null;
517                    try
518                      {
519                        sig = new PKCS7SignedData(super.getInputStream(ze));
520                      }
521                    catch (CertificateException ce)
522                      {
523                        IOException ioe = new IOException("certificate parsing error");
524                        ioe.initCause(ce);
525                        throw ioe;
526                      }
527                    catch (CRLException crle)
528                      {
529                        IOException ioe = new IOException("CRL parsing error");
530                        ioe.initCause(crle);
531                        throw ioe;
532                      }
533                    if (name.endsWith(PKCS7_DSA_SUFFIX))
534                      pkcs7Dsa.put(alias, sig);
535                    else if (name.endsWith(PKCS7_RSA_SUFFIX))
536                      pkcs7Rsa.put(alias, sig);
537                  }
538                else if (name.endsWith(SF_SUFFIX))
539                  {
540                    if (DEBUG)
541                      debug("reading signature file for " + alias + ": " + name);
542                    Manifest sf = new Manifest(super.getInputStream(ze));
543                    sigFiles.put(alias, sf);
544                    if (DEBUG)
545                      debug("result: " + sf);
546                  }
547              }
548          }
549    
550        // Phase 2: verify the signatures on any signature files.
551        Set validCerts = new HashSet();
552        Map entryCerts = new HashMap();
553        for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); )
554          {
555            int valid = 0;
556            Map.Entry e = (Map.Entry) it.next();
557            String alias = (String) e.getKey();
558    
559            PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias);
560            if (sig != null)
561              {
562                Certificate[] certs = sig.getCertificates();
563                Set signerInfos = sig.getSignerInfos();
564                for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
565                  verify(certs, (SignerInfo) it2.next(), alias, validCerts);
566              }
567    
568            sig = (PKCS7SignedData) pkcs7Rsa.get(alias);
569            if (sig != null)
570              {
571                Certificate[] certs = sig.getCertificates();
572                Set signerInfos = sig.getSignerInfos();
573                for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
574                  verify(certs, (SignerInfo) it2.next(), alias, validCerts);
575              }
576    
577            // It isn't a signature for anything. Punt it.
578            if (validCerts.isEmpty())
579              {
580                it.remove();
581                continue;
582              }
583    
584            entryCerts.put(e.getValue(), new HashSet(validCerts));
585            validCerts.clear();
586          }
587    
588        // Read the manifest into a HashMap (String fileName, String entry)
589        // The fileName might be split into multiple lines in the manifest.
590        // Such additional lines will start with a space.
591        InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
592        ByteArrayOutputStream baStream = new ByteArrayOutputStream();
593        byte[] ba = new byte[1024];
594        while (true)
595          {
596            int len = in.read(ba);
597            if (len < 0)
598              break;
599            baStream.write(ba, 0, len);
600          }
601        in.close();
602    
603        HashMap hmManifestEntries = new HashMap();
604        Pattern p = Pattern.compile("Name: (.+?\r?\n(?: .+?\r?\n)*)"
605                                    + ".+?-Digest: .+?\r?\n\r?\n");
606        Matcher m = p.matcher(baStream.toString());
607        while (m.find())
608          {
609            String fileName = m.group(1).replaceAll("\r?\n ?", "");
610            hmManifestEntries.put(fileName, m.group());
611          }
612    
613        // Phase 3: verify the signature file signatures against the manifest,
614        // mapping the entry name to the target certificates.
615        this.entryCerts = new HashMap();
616        for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); )
617          {
618            Map.Entry e = (Map.Entry) it.next();
619            Manifest sigfile = (Manifest) e.getKey();
620            Map entries = sigfile.getEntries();
621            Set certificates = (Set) e.getValue();
622    
623            for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); )
624              {
625                Map.Entry e2 = (Map.Entry) it2.next();
626                String entryname = String.valueOf(e2.getKey());
627                Attributes attr = (Attributes) e2.getValue();
628                if (verifyHashes(entryname, attr, hmManifestEntries))
629                  {
630                    if (DEBUG)
631                      debug("entry " + entryname + " has certificates " + certificates);
632                    Set s = (Set) this.entryCerts.get(entryname);
633                    if (s != null)
634                      s.addAll(certificates);
635                    else
636                      this.entryCerts.put(entryname, new HashSet(certificates));
637                  }
638              }
639          }
640    
641        signaturesRead = true;
642      }
643    
644      /**
645       * Tell if the given signer info is over the given alias's signature file,
646       * given one of the certificates specified.
647       */
648      private void verify(Certificate[] certs, SignerInfo signerInfo,
649                          String alias, Set validCerts)
650      {
651        Signature sig = null;
652        try
653          {
654            OID alg = signerInfo.getDigestEncryptionAlgorithmId();
655            if (alg.equals(DSA_ENCRYPTION_OID))
656              {
657                if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID))
658                  return;
659                sig = Signature.getInstance("SHA1withDSA", provider);
660              }
661            else if (alg.equals(RSA_ENCRYPTION_OID))
662              {
663                OID hash = signerInfo.getDigestAlgorithmId();
664                if (hash.equals(MD2_OID))
665                  sig = Signature.getInstance("md2WithRsaEncryption", provider);
666                else if (hash.equals(MD4_OID))
667                  sig = Signature.getInstance("md4WithRsaEncryption", provider);
668                else if (hash.equals(MD5_OID))
669                  sig = Signature.getInstance("md5WithRsaEncryption", provider);
670                else if (hash.equals(SHA1_OID))
671                  sig = Signature.getInstance("sha1WithRsaEncryption", provider);
672                else
673                  return;
674              }
675            else
676              {
677                if (DEBUG)
678                  debug("unsupported signature algorithm: " + alg);
679                return;
680              }
681          }
682        catch (NoSuchAlgorithmException nsae)
683          {
684            if (DEBUG)
685              {
686                debug(nsae);
687                nsae.printStackTrace();
688              }
689            return;
690          }
691        ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX);
692        if (sigFileEntry == null)
693          return;
694        for (int i = 0; i < certs.length; i++)
695          {
696            if (!(certs[i] instanceof X509Certificate))
697              continue;
698            X509Certificate cert = (X509Certificate) certs[i];
699            if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) ||
700                !cert.getSerialNumber().equals(signerInfo.getSerialNumber()))
701              continue;
702            try
703              {
704                sig.initVerify(cert.getPublicKey());
705                InputStream in = super.getInputStream(sigFileEntry);
706                if (in == null)
707                  continue;
708                byte[] buf = new byte[1024];
709                int len = 0;
710                while ((len = in.read(buf)) != -1)
711                  sig.update(buf, 0, len);
712                if (sig.verify(signerInfo.getEncryptedDigest()))
713                  {
714                    if (DEBUG)
715                      debug("signature for " + cert.getSubjectDN() + " is good");
716                    validCerts.add(cert);
717                  }
718              }
719            catch (IOException ioe)
720              {
721                continue;
722              }
723            catch (InvalidKeyException ike)
724              {
725                continue;
726              }
727            catch (SignatureException se)
728              {
729                continue;
730              }
731          }
732      }
733    
734      /**
735       * Verifies that the digest(s) in a signature file were, in fact, made over
736       * the manifest entry for ENTRY.
737       *
738       * @param entry The entry name.
739       * @param attr The attributes from the signature file to verify.
740       * @param hmManifestEntries Mappings of Jar file entry names to their manifest
741       *          entry text; i.e. the base-64 encoding of their
742       */
743      private boolean verifyHashes(String entry, Attributes attr,
744                                   HashMap hmManifestEntries)
745      {
746        int verified = 0;
747    
748        String stringEntry = (String) hmManifestEntries.get(entry);
749        if (stringEntry == null)
750          {
751            if (DEBUG)
752              debug("could not find " + entry + " in manifest");
753            return false;
754          }
755        // The bytes for ENTRY's manifest entry, which are signed in the
756        // signature file.
757        byte[] entryBytes = stringEntry.getBytes();
758    
759        for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
760          {
761            Map.Entry e = (Map.Entry) it.next();
762            String key = String.valueOf(e.getKey());
763            if (!key.endsWith(DIGEST_KEY_SUFFIX))
764              continue;
765            String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length());
766            try
767              {
768                byte[] hash = Base64InputStream.decode((String) e.getValue());
769                MessageDigest md = (MessageDigest) digestAlgorithms.get(alg);
770                if (md == null)
771                  {
772                    md = MessageDigest.getInstance(alg, provider);
773                    digestAlgorithms.put(alg, md);
774                  }
775                md.reset();
776                byte[] hash2 = md.digest(entryBytes);
777                if (DEBUG)
778                  debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm()
779                        + " expect=" + new java.math.BigInteger(hash).toString(16)
780                        + " comp=" + new java.math.BigInteger(hash2).toString(16));
781                if (!Arrays.equals(hash, hash2))
782                  return false;
783                verified++;
784              }
785            catch (IOException ioe)
786              {
787                if (DEBUG)
788                  {
789                    debug(ioe);
790                    ioe.printStackTrace();
791                  }
792                return false;
793              }
794            catch (NoSuchAlgorithmException nsae)
795              {
796                if (DEBUG)
797                  {
798                    debug(nsae);
799                    nsae.printStackTrace();
800                  }
801                return false;
802              }
803          }
804    
805        // We have to find at least one valid digest.
806        return verified > 0;
807      }
808    
809      /**
810       * A utility class that verifies jar entries as they are read.
811       */
812      private static class EntryInputStream extends FilterInputStream
813      {
814        private final JarFile jarfile;
815        private final long length;
816        private long pos;
817        private final ZipEntry entry;
818        private final byte[][] hashes;
819        private final MessageDigest[] md;
820        private boolean checked;
821    
822        EntryInputStream(final ZipEntry entry,
823                         final InputStream in,
824                         final JarFile jar)
825          throws IOException
826        {
827          super(in);
828          this.entry = entry;
829          this.jarfile = jar;
830    
831          length = entry.getSize();
832          pos = 0;
833          checked = false;
834    
835          Attributes attr;
836          Manifest manifest = jarfile.getManifest();
837          if (manifest != null)
838            attr = manifest.getAttributes(entry.getName());
839          else
840            attr = null;
841          if (DEBUG)
842            debug("verifying entry " + entry + " attr=" + attr);
843          if (attr == null)
844            {
845              hashes = new byte[0][];
846              md = new MessageDigest[0];
847            }
848          else
849            {
850              List hashes = new LinkedList();
851              List md = new LinkedList();
852              for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
853                {
854                  Map.Entry e = (Map.Entry) it.next();
855                  String key = String.valueOf(e.getKey());
856                  if (key == null)
857                    continue;
858                  if (!key.endsWith(DIGEST_KEY_SUFFIX))
859                    continue;
860                  hashes.add(Base64InputStream.decode((String) e.getValue()));
861                  try
862                    {
863                      int length = key.length() - DIGEST_KEY_SUFFIX.length();
864                      String alg = key.substring(0, length);
865                      md.add(MessageDigest.getInstance(alg, provider));
866                    }
867                  catch (NoSuchAlgorithmException nsae)
868                    {
869                      IOException ioe = new IOException("no such message digest: " + key);
870                      ioe.initCause(nsae);
871                      throw ioe;
872                    }
873                }
874              if (DEBUG)
875                debug("digests=" + md);
876              this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]);
877              this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]);
878            }
879        }
880    
881        public boolean markSupported()
882        {
883          return false;
884        }
885    
886        public void mark(int readLimit)
887        {
888        }
889    
890        public void reset()
891        {
892        }
893    
894        public int read() throws IOException
895        {
896          int b = super.read();
897          if (b == -1)
898            {
899              eof();
900              return -1;
901            }
902          for (int i = 0; i < md.length; i++)
903            md[i].update((byte) b);
904          pos++;
905          if (length > 0 && pos >= length)
906            eof();
907          return b;
908        }
909    
910        public int read(byte[] buf, int off, int len) throws IOException
911        {
912          int count = super.read(buf, off, (int) Math.min(len, (length != 0
913                                                                ? length - pos
914                                                                : Integer.MAX_VALUE)));
915          if (count == -1 || (length > 0 && pos >= length))
916            {
917              eof();
918              return -1;
919            }
920          for (int i = 0; i < md.length; i++)
921            md[i].update(buf, off, count);
922          pos += count;
923          if (length != 0 && pos >= length)
924            eof();
925          return count;
926        }
927    
928        public int read(byte[] buf) throws IOException
929        {
930          return read(buf, 0, buf.length);
931        }
932    
933        public long skip(long bytes) throws IOException
934        {
935          byte[] b = new byte[1024];
936          long amount = 0;
937          while (amount < bytes)
938            {
939              int l = read(b, 0, (int) Math.min(b.length, bytes - amount));
940              if (l == -1)
941                break;
942              amount += l;
943            }
944          return amount;
945        }
946    
947        private void eof() throws IOException
948        {
949          if (checked)
950            return;
951          checked = true;
952          for (int i = 0; i < md.length; i++)
953            {
954              byte[] hash = md[i].digest();
955              if (DEBUG)
956                debug("verifying " + md[i].getAlgorithm() + " expect="
957                      + new java.math.BigInteger(hashes[i]).toString(16)
958                      + " comp=" + new java.math.BigInteger(hash).toString(16));
959              if (!Arrays.equals(hash, hashes[i]))
960                {
961                  synchronized(jarfile)
962                    {
963                      if (DEBUG)
964                        debug(entry + " could NOT be verified");
965                      jarfile.verified.put(entry.getName(), Boolean.FALSE);
966                    }
967                  return;
968                  // XXX ??? what do we do here?
969                  // throw new ZipException("message digest mismatch");
970                }
971            }
972    
973          synchronized(jarfile)
974            {
975              if (DEBUG)
976                debug(entry + " has been VERIFIED");
977              jarfile.verified.put(entry.getName(), Boolean.TRUE);
978            }
979        }
980      }
981    }