OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / util / jar / JarFile.java
1 /* JarFile.java - Representation of a jar file
2    Copyright (C) 2000, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package java.util.jar;
40
41 import gnu.java.io.Base64InputStream;
42 import gnu.java.security.OID;
43 import gnu.java.security.pkcs.PKCS7SignedData;
44 import gnu.java.security.pkcs.SignerInfo;
45 import gnu.java.security.provider.Gnu;
46
47 import java.io.ByteArrayOutputStream;
48 import java.io.File;
49 import java.io.FileNotFoundException;
50 import java.io.FilterInputStream;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.security.InvalidKeyException;
54 import java.security.MessageDigest;
55 import java.security.NoSuchAlgorithmException;
56 import java.security.Signature;
57 import java.security.SignatureException;
58 import java.security.cert.CRLException;
59 import java.security.cert.Certificate;
60 import java.security.cert.CertificateException;
61 import java.security.cert.X509Certificate;
62 import java.util.Arrays;
63 import java.util.Enumeration;
64 import java.util.HashMap;
65 import java.util.HashSet;
66 import java.util.Iterator;
67 import java.util.LinkedList;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.Set;
71 import java.util.regex.Matcher;
72 import java.util.regex.Pattern;
73 import java.util.zip.ZipEntry;
74 import java.util.zip.ZipException;
75 import java.util.zip.ZipFile;
76
77 /**
78  * Representation of a jar file.
79  * <p>
80  * Note that this class is not a subclass of java.io.File but a subclass of
81  * java.util.zip.ZipFile and you can only read JarFiles with it (although
82  * there are constructors that take a File object).
83  *
84  * @since 1.2
85  * @author Mark Wielaard (mark@klomp.org)
86  * @author Casey Marshall (csm@gnu.org) wrote the certificate and entry
87  *  verification code.
88  */
89 public class JarFile extends ZipFile
90 {
91   // Fields
92
93   /** The name of the manifest entry: META-INF/MANIFEST.MF */
94   public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
95
96   /** The META-INF directory entry. */
97   private static final String META_INF = "META-INF/";
98
99   /** 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 }