OSDN Git Service

Imported GNU Classpath 0.90
[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.zip.ZipEntry;
72 import java.util.zip.ZipException;
73 import java.util.zip.ZipFile;
74
75 /**
76  * Representation of a jar file.
77  * <p>
78  * Note that this class is not a subclass of java.io.File but a subclass of
79  * java.util.zip.ZipFile and you can only read JarFiles with it (although
80  * there are constructors that take a File object).
81  *
82  * @since 1.2
83  * @author Mark Wielaard (mark@klomp.org)
84  * @author Casey Marshall (csm@gnu.org) wrote the certificate and entry
85  *  verification code.
86  */
87 public class JarFile extends ZipFile
88 {
89   // Fields
90
91   /** The name of the manifest entry: META-INF/MANIFEST.MF */
92   public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
93
94   /** The META-INF directory entry. */
95   private static final String META_INF = "META-INF/";
96
97   /** The suffix for PKCS7 DSA signature entries. */
98   private static final String PKCS7_DSA_SUFFIX = ".DSA";
99
100   /** The suffix for PKCS7 RSA signature entries. */
101   private static final String PKCS7_RSA_SUFFIX = ".RSA";
102
103   /** The suffix for digest attributes. */
104   private static final String DIGEST_KEY_SUFFIX = "-Digest";
105
106   /** The suffix for signature files. */
107   private static final String SF_SUFFIX = ".SF";
108
109   /**
110    * The security provider to use for signature verification.
111    * We need a known fallback to be able to read any signed jar file
112    * (which might contain the user selected security provider).
113    * This is package-private to avoid accessor methods for inner classes.
114    */
115   static final Gnu provider = new Gnu();
116
117   // Signature OIDs.
118   private static final OID MD2_OID = new OID("1.2.840.113549.2.2");
119   private static final OID MD4_OID = new OID("1.2.840.113549.2.4");
120   private static final OID MD5_OID = new OID("1.2.840.113549.2.5");
121   private static final OID SHA1_OID = new OID("1.3.14.3.2.26");
122   private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1");
123   private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1");
124
125   /**
126    * The manifest of this file, if any, otherwise null.
127    * Read when first needed.
128    */
129   private Manifest manifest;
130
131   /** Whether to verify the manifest and all entries. */
132   boolean verify;
133
134   /** Whether the has already been loaded. */
135   private boolean manifestRead = false;
136
137   /** Whether the signature files have been loaded. */
138   boolean signaturesRead = false;
139
140   /**
141    * A map between entry names and booleans, signaling whether or
142    * not that entry has been verified.
143    * Only be accessed with lock on this JarFile*/
144   HashMap verified = new HashMap();
145
146   /**
147    * A mapping from entry name to certificates, if any.
148    * Only accessed with lock on this JarFile.
149    */
150   HashMap entryCerts;
151
152   static boolean DEBUG = false;
153   static void debug(Object msg)
154   {
155     System.err.print(JarFile.class.getName());
156     System.err.print(" >>> ");
157     System.err.println(msg);
158   }
159
160   // Constructors
161
162   /**
163    * Creates a new JarFile. All jar entries are verified (when a Manifest file
164    * for this JarFile exists). You need to actually open and read the complete
165    * jar entry (with <code>getInputStream()</code>) to check its signature.
166    *
167    * @param fileName the name of the file to open
168    * @exception FileNotFoundException if the fileName cannot be found
169    * @exception IOException if another IO exception occurs while reading
170    */
171   public JarFile(String fileName) throws FileNotFoundException, IOException
172   {
173     this(fileName, true);
174   }
175
176   /**
177    * Creates a new JarFile. If verify is true then all jar entries are
178    * verified (when a Manifest file for this JarFile exists). You need to
179    * actually open and read the complete jar entry
180    * (with <code>getInputStream()</code>) to check its signature.
181    *
182    * @param fileName the name of the file to open
183    * @param verify checks manifest and entries when true and a manifest
184    * exists, when false no checks are made
185    * @exception FileNotFoundException if the fileName cannot be found
186    * @exception IOException if another IO exception occurs while reading
187    */
188   public JarFile(String fileName, boolean verify) throws
189     FileNotFoundException, IOException
190   {
191     super(fileName);
192     if (verify)
193       {
194         manifest = readManifest();
195         verify();
196       }
197   }
198
199   /**
200    * Creates a new JarFile. All jar entries are verified (when a Manifest file
201    * for this JarFile exists). You need to actually open and read the complete
202    * jar entry (with <code>getInputStream()</code>) to check its signature.
203    *
204    * @param file the file to open as a jar file
205    * @exception FileNotFoundException if the file does not exits
206    * @exception IOException if another IO exception occurs while reading
207    */
208   public JarFile(File file) throws FileNotFoundException, IOException
209   {
210     this(file, true);
211   }
212
213   /**
214    * Creates a new JarFile. If verify is true then all jar entries are
215    * verified (when a Manifest file for this JarFile exists). You need to
216    * actually open and read the complete jar entry
217    * (with <code>getInputStream()</code>) to check its signature.
218    *
219    * @param file the file to open to open as a jar file
220    * @param verify checks manifest and entries when true and a manifest
221    * exists, when false no checks are made
222    * @exception FileNotFoundException if file does not exist
223    * @exception IOException if another IO exception occurs while reading
224    */
225   public JarFile(File file, boolean verify) throws FileNotFoundException,
226     IOException
227   {
228     super(file);
229     if (verify)
230       {
231         manifest = readManifest();
232         verify();
233       }
234   }
235
236   /**
237    * Creates a new JarFile with the indicated mode. If verify is true then
238    * all jar entries are verified (when a Manifest file for this JarFile
239    * exists). You need to actually open and read the complete jar entry
240    * (with <code>getInputStream()</code>) to check its signature.
241    * manifest and if the manifest exists and verify is true verfies it.
242    *
243    * @param file the file to open to open as a jar file
244    * @param verify checks manifest and entries when true and a manifest
245    * exists, when false no checks are made
246    * @param mode either ZipFile.OPEN_READ or
247    *             (ZipFile.OPEN_READ | ZipFile.OPEN_DELETE)
248    * @exception FileNotFoundException if the file does not exist
249    * @exception IOException if another IO exception occurs while reading
250    * @exception IllegalArgumentException when given an illegal mode
251    * 
252    * @since 1.3
253    */
254   public JarFile(File file, boolean verify, int mode) throws
255     FileNotFoundException, IOException, IllegalArgumentException
256   {
257     super(file, mode);
258     if (verify)
259       {
260         manifest = readManifest();
261         verify();
262       }
263   }
264
265   // Methods
266
267   /**
268    * XXX - should verify the manifest file
269    */
270   private void verify()
271   {
272     // only check if manifest is not null
273     if (manifest == null)
274       {
275         verify = false;
276         return;
277       }
278
279     verify = true;
280     // XXX - verify manifest
281   }
282
283   /**
284    * Parses and returns the manifest if it exists, otherwise returns null.
285    */
286   private Manifest readManifest()
287   {
288     try
289       {
290         ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
291         if (manEntry != null)
292           {
293             InputStream in = super.getInputStream(manEntry);
294             manifestRead = true;
295             return new Manifest(in);
296           }
297         else
298           {
299             manifestRead = true;
300             return null;
301           }
302       }
303     catch (IOException ioe)
304       {
305         manifestRead = true;
306         return null;
307       }
308   }
309
310   /**
311    * Returns a enumeration of all the entries in the JarFile.
312    * Note that also the Jar META-INF entries are returned.
313    *
314    * @exception IllegalStateException when the JarFile is already closed
315    */
316   public Enumeration entries() throws IllegalStateException
317   {
318     return new JarEnumeration(super.entries(), this);
319   }
320
321   /**
322    * Wraps a given Zip Entries Enumeration. For every zip entry a
323    * JarEntry is created and the corresponding Attributes are looked up.
324    */
325   private static class JarEnumeration implements Enumeration
326   {
327
328     private final Enumeration entries;
329     private final JarFile jarfile;
330
331     JarEnumeration(Enumeration e, JarFile f)
332     {
333       entries = e;
334       jarfile = f;
335     }
336
337     public boolean hasMoreElements()
338     {
339       return entries.hasMoreElements();
340     }
341
342     public Object nextElement()
343     {
344       ZipEntry zip = (ZipEntry) entries.nextElement();
345       JarEntry jar = new JarEntry(zip);
346       Manifest manifest;
347       try
348         {
349           manifest = jarfile.getManifest();
350         }
351       catch (IOException ioe)
352         {
353           manifest = null;
354         }
355
356       if (manifest != null)
357         {
358           jar.attr = manifest.getAttributes(jar.getName());
359         }
360
361       synchronized(jarfile)
362         {
363           if (jarfile.verify && !jarfile.signaturesRead)
364             try
365               {
366                 jarfile.readSignatures();
367               }
368             catch (IOException ioe)
369               {
370                 if (JarFile.DEBUG)
371                   {
372                     JarFile.debug(ioe);
373                     ioe.printStackTrace();
374                   }
375                 jarfile.signaturesRead = true; // fudge it.
376               }
377
378           // Include the certificates only if we have asserted that the
379           // signatures are valid. This means the certificates will not be
380           // available if the entry hasn't been read yet.
381           if (jarfile.entryCerts != null
382               && jarfile.verified.get(zip.getName()) == Boolean.TRUE)
383             {
384               Set certs = (Set) jarfile.entryCerts.get(jar.getName());
385               if (certs != null)
386                 jar.certs = (Certificate[])
387                   certs.toArray(new Certificate[certs.size()]);
388             }
389         }
390       return jar;
391     }
392   }
393
394   /**
395    * XXX
396    * It actually returns a JarEntry not a zipEntry
397    * @param name XXX
398    */
399   public synchronized ZipEntry getEntry(String name)
400   {
401     ZipEntry entry = super.getEntry(name);
402     if (entry != null)
403       {
404         JarEntry jarEntry = new JarEntry(entry);
405         Manifest manifest;
406         try
407           {
408             manifest = getManifest();
409           }
410         catch (IOException ioe)
411           {
412             manifest = null;
413           }
414
415         if (manifest != null)
416           {
417             jarEntry.attr = manifest.getAttributes(name);
418           }
419
420         if (verify && !signaturesRead)
421           try
422             {
423               readSignatures();
424             }
425           catch (IOException ioe)
426             {
427               if (DEBUG)
428                 {
429                   debug(ioe);
430                   ioe.printStackTrace();
431                 }
432               signaturesRead = true;
433             }
434         // See the comments in the JarEnumeration for why we do this
435         // check.
436         if (DEBUG)
437           debug("entryCerts=" + entryCerts + " verified " + name
438                 + " ? " + verified.get(name));
439         if (entryCerts != null && verified.get(name) == Boolean.TRUE)
440           {
441             Set certs = (Set) entryCerts.get(name);
442             if (certs != null)
443               jarEntry.certs = (Certificate[])
444                 certs.toArray(new Certificate[certs.size()]);
445           }
446         return jarEntry;
447       }
448     return null;
449   }
450
451   /**
452    * Returns an input stream for the given entry. If configured to
453    * verify entries, the input stream returned will verify them while
454    * the stream is read, but only on the first time.
455    *
456    * @param entry The entry to get the input stream for.
457    * @exception ZipException XXX
458    * @exception IOException XXX
459    */
460   public synchronized InputStream getInputStream(ZipEntry entry) throws
461     ZipException, IOException
462   {
463     // If we haven't verified the hash, do it now.
464     if (!verified.containsKey(entry.getName()) && verify)
465       {
466         if (DEBUG)
467           debug("reading and verifying " + entry);
468         return new EntryInputStream(entry, super.getInputStream(entry), this);
469       }
470     else
471       {
472         if (DEBUG)
473           debug("reading already verified entry " + entry);
474         if (verify && verified.get(entry.getName()) == Boolean.FALSE)
475           throw new ZipException("digest for " + entry + " is invalid");
476         return super.getInputStream(entry);
477       }
478   }
479
480   /**
481    * Returns the JarEntry that belongs to the name if such an entry
482    * exists in the JarFile. Returns null otherwise
483    * Convenience method that just casts the result from <code>getEntry</code>
484    * to a JarEntry.
485    *
486    * @param name the jar entry name to look up
487    * @return the JarEntry if it exists, null otherwise
488    */
489   public JarEntry getJarEntry(String name)
490   {
491     return (JarEntry) getEntry(name);
492   }
493
494   /**
495    * Returns the manifest for this JarFile or null when the JarFile does not
496    * contain a manifest file.
497    */
498   public synchronized Manifest getManifest() throws IOException
499   {
500     if (!manifestRead)
501       manifest = readManifest();
502
503     return manifest;
504   }
505
506   // Only called with lock on this JarFile.
507   // Package private for use in inner classes.
508   void readSignatures() throws IOException
509   {
510     Map pkcs7Dsa = new HashMap();
511     Map pkcs7Rsa = new HashMap();
512     Map sigFiles = new HashMap();
513
514     // Phase 1: Read all signature files. These contain the user
515     // certificates as well as the signatures themselves.
516     for (Enumeration e = super.entries(); e.hasMoreElements(); )
517       {
518         ZipEntry ze = (ZipEntry) e.nextElement();
519         String name = ze.getName();
520         if (name.startsWith(META_INF))
521           {
522             String alias = name.substring(META_INF.length());
523             if (alias.lastIndexOf('.') >= 0)
524               alias = alias.substring(0, alias.lastIndexOf('.'));
525
526             if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX))
527               {
528                 if (DEBUG)
529                   debug("reading PKCS7 info from " + name + ", alias=" + alias);
530                 PKCS7SignedData sig = null;
531                 try
532                   {
533                     sig = new PKCS7SignedData(super.getInputStream(ze));
534                   }
535                 catch (CertificateException ce)
536                   {
537                     IOException ioe = new IOException("certificate parsing error");
538                     ioe.initCause(ce);
539                     throw ioe;
540                   }
541                 catch (CRLException crle)
542                   {
543                     IOException ioe = new IOException("CRL parsing error");
544                     ioe.initCause(crle);
545                     throw ioe;
546                   }
547                 if (name.endsWith(PKCS7_DSA_SUFFIX))
548                   pkcs7Dsa.put(alias, sig);
549                 else if (name.endsWith(PKCS7_RSA_SUFFIX))
550                   pkcs7Rsa.put(alias, sig);
551               }
552             else if (name.endsWith(SF_SUFFIX))
553               {
554                 if (DEBUG)
555                   debug("reading signature file for " + alias + ": " + name);
556                 Manifest sf = new Manifest(super.getInputStream(ze));
557                 sigFiles.put(alias, sf);
558                 if (DEBUG)
559                   debug("result: " + sf);
560               }
561           }
562       }
563
564     // Phase 2: verify the signatures on any signature files.
565     Set validCerts = new HashSet();
566     Map entryCerts = new HashMap();
567     for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); )
568       {
569         int valid = 0;
570         Map.Entry e = (Map.Entry) it.next();
571         String alias = (String) e.getKey();
572
573         PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias);
574         if (sig != null)
575           {
576             Certificate[] certs = sig.getCertificates();
577             Set signerInfos = sig.getSignerInfos();
578             for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
579               verify(certs, (SignerInfo) it2.next(), alias, validCerts);
580           }
581
582         sig = (PKCS7SignedData) pkcs7Rsa.get(alias);
583         if (sig != null)
584           {
585             Certificate[] certs = sig.getCertificates();
586             Set signerInfos = sig.getSignerInfos();
587             for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
588               verify(certs, (SignerInfo) it2.next(), alias, validCerts);
589           }
590
591         // It isn't a signature for anything. Punt it.
592         if (validCerts.isEmpty())
593           {
594             it.remove();
595             continue;
596           }
597
598         entryCerts.put(e.getValue(), new HashSet(validCerts));
599         validCerts.clear();
600       }
601
602     // Phase 3: verify the signature file signatures against the manifest,
603     // mapping the entry name to the target certificates.
604     this.entryCerts = new HashMap();
605     for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); )
606       {
607         Map.Entry e = (Map.Entry) it.next();
608         Manifest sigfile = (Manifest) e.getKey();
609         Map entries = sigfile.getEntries();
610         Set certificates = (Set) e.getValue();
611
612         for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); )
613           {
614             Map.Entry e2 = (Map.Entry) it2.next();
615             String entryname = String.valueOf(e2.getKey());
616             Attributes attr = (Attributes) e2.getValue();
617             if (verifyHashes(entryname, attr))
618               {
619                 if (DEBUG)
620                   debug("entry " + entryname + " has certificates " + certificates);
621                 Set s = (Set) this.entryCerts.get(entryname);
622                 if (s != null)
623                   s.addAll(certificates);
624                 else
625                   this.entryCerts.put(entryname, new HashSet(certificates));
626               }
627           }
628       }
629
630     signaturesRead = true;
631   }
632
633   /**
634    * Tell if the given signer info is over the given alias's signature file,
635    * given one of the certificates specified.
636    */
637   private void verify(Certificate[] certs, SignerInfo signerInfo,
638                       String alias, Set validCerts)
639   {
640     Signature sig = null;
641     try
642       {
643         OID alg = signerInfo.getDigestEncryptionAlgorithmId();
644         if (alg.equals(DSA_ENCRYPTION_OID))
645           {
646             if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID))
647               return;
648             sig = Signature.getInstance("SHA1withDSA", provider);
649           }
650         else if (alg.equals(RSA_ENCRYPTION_OID))
651           {
652             OID hash = signerInfo.getDigestAlgorithmId();
653             if (hash.equals(MD2_OID))
654               sig = Signature.getInstance("md2WithRsaEncryption", provider);
655             else if (hash.equals(MD4_OID))
656               sig = Signature.getInstance("md4WithRsaEncryption", provider);
657             else if (hash.equals(MD5_OID))
658               sig = Signature.getInstance("md5WithRsaEncryption", provider);
659             else if (hash.equals(SHA1_OID))
660               sig = Signature.getInstance("sha1WithRsaEncryption", provider);
661             else
662               return;
663           }
664         else
665           {
666             if (DEBUG)
667               debug("unsupported signature algorithm: " + alg);
668             return;
669           }
670       }
671     catch (NoSuchAlgorithmException nsae)
672       {
673         if (DEBUG)
674           {
675             debug(nsae);
676             nsae.printStackTrace();
677           }
678         return;
679       }
680     ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX);
681     if (sigFileEntry == null)
682       return;
683     for (int i = 0; i < certs.length; i++)
684       {
685         if (!(certs[i] instanceof X509Certificate))
686           continue;
687         X509Certificate cert = (X509Certificate) certs[i];
688         if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) ||
689             !cert.getSerialNumber().equals(signerInfo.getSerialNumber()))
690           continue;
691         try
692           {
693             sig.initVerify(cert.getPublicKey());
694             InputStream in = super.getInputStream(sigFileEntry);
695             if (in == null)
696               continue;
697             byte[] buf = new byte[1024];
698             int len = 0;
699             while ((len = in.read(buf)) != -1)
700               sig.update(buf, 0, len);
701             if (sig.verify(signerInfo.getEncryptedDigest()))
702               {
703                 if (DEBUG)
704                   debug("signature for " + cert.getSubjectDN() + " is good");
705                 validCerts.add(cert);
706               }
707           }
708         catch (IOException ioe)
709           {
710             continue;
711           }
712         catch (InvalidKeyException ike)
713           {
714             continue;
715           }
716         catch (SignatureException se)
717           {
718             continue;
719           }
720       }
721   }
722
723   /**
724    * Verifies that the digest(s) in a signature file were, in fact, made
725    * over the manifest entry for ENTRY.
726    *
727    * @param entry The entry name.
728    * @param attr The attributes from the signature file to verify.
729    */
730   private boolean verifyHashes(String entry, Attributes attr)
731   {
732     int verified = 0;
733
734     // The bytes for ENTRY's manifest entry, which are signed in the
735     // signature file.
736     byte[] entryBytes = null;
737     try
738       {
739         ZipEntry e = super.getEntry(entry);
740         if (e == null)
741           {
742             if (DEBUG)
743               debug("verifyHashes: no entry '" + entry + "'");
744             return false;
745           }
746         entryBytes = readManifestEntry(e);
747       }
748     catch (IOException ioe)
749       {
750         if (DEBUG)
751           {
752             debug(ioe);
753             ioe.printStackTrace();
754           }
755         return false;
756       }
757
758     for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
759       {
760         Map.Entry e = (Map.Entry) it.next();
761         String key = String.valueOf(e.getKey());
762         if (!key.endsWith(DIGEST_KEY_SUFFIX))
763           continue;
764         String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length());
765         try
766           {
767             byte[] hash = Base64InputStream.decode((String) e.getValue());
768             MessageDigest md = MessageDigest.getInstance(alg, provider);
769             md.update(entryBytes);
770             byte[] hash2 = md.digest();
771             if (DEBUG)
772               debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm()
773                     + " expect=" + new java.math.BigInteger(hash).toString(16)
774                     + " comp=" + new java.math.BigInteger(hash2).toString(16));
775             if (!Arrays.equals(hash, hash2))
776               return false;
777             verified++;
778           }
779         catch (IOException ioe)
780           {
781             if (DEBUG)
782               {
783                 debug(ioe);
784                 ioe.printStackTrace();
785               }
786             return false;
787           }
788         catch (NoSuchAlgorithmException nsae)
789           {
790             if (DEBUG)
791               {
792                 debug(nsae);
793                 nsae.printStackTrace();
794               }
795             return false;
796           }
797       }
798
799     // We have to find at least one valid digest.
800     return verified > 0;
801   }
802
803   /**
804    * Read the raw bytes that comprise a manifest entry. We can't use the
805    * Manifest object itself, because that loses information (such as line
806    * endings, and order of entries).
807    */
808   private byte[] readManifestEntry(ZipEntry entry) throws IOException
809   {
810     InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
811     ByteArrayOutputStream out = new ByteArrayOutputStream();
812     byte[] target = ("Name: " + entry.getName()).getBytes();
813     int t = 0, c, prev = -1, state = 0, l = -1;
814
815     while ((c = in.read()) != -1)
816       {
817 //         if (DEBUG)
818 //           debug("read "
819 //                 + (c == '\n' ? "\\n" : (c == '\r' ? "\\r" : String.valueOf((char) c)))
820 //                 + " state=" + state + " prev="
821 //                 + (prev == '\n' ? "\\n" : (prev == '\r' ? "\\r" : String.valueOf((char) prev)))
822 //                 + " t=" + t + (t < target.length ? (" target[t]=" + (char) target[t]) : "")
823 //                 + " l=" + l);
824         switch (state)
825           {
826
827           // Step 1: read until we find the "target" bytes: the start
828           // of the entry we need to read.
829           case 0:
830             if (((byte) c) != target[t])
831               t = 0;
832             else
833               {
834                 t++;
835                 if (t == target.length)
836                   {
837                     out.write(target);
838                     state = 1;
839                   }
840               }
841             break;
842
843           // Step 2: assert that there is a newline character after
844           // the "target" bytes.
845           case 1:
846             if (c != '\n' && c != '\r')
847               {
848                 out.reset();
849                 t = 0;
850                 state = 0;
851               }
852             else
853               {
854                 out.write(c);
855                 state = 2;
856               }
857             break;
858
859           // Step 3: read this whole entry, until we reach an empty
860           // line.
861           case 2:
862             if (c == '\n')
863               {
864                 out.write(c);
865                 // NL always terminates a line.
866                 if (l == 0 || (l == 1 && prev == '\r'))
867                   return out.toByteArray();
868                 l = 0;
869               }
870             else
871               {
872                 // Here we see a blank line terminated by a CR,
873                 // followed by the next entry. Technically, `c' should
874                 // always be 'N' at this point.
875                 if (l == 1 && prev == '\r')
876                   return out.toByteArray();
877                 out.write(c);
878                 l++;
879               }
880             prev = c;
881             break;
882
883           default:
884             throw new RuntimeException("this statement should be unreachable");
885           }
886       }
887
888     // The last entry, with a single CR terminating the line.
889     if (state == 2 && prev == '\r' && l == 0)
890       return out.toByteArray();
891
892     // We should not reach this point, we didn't find the entry (or, possibly,
893     // it is the last entry and is malformed).
894     throw new IOException("could not find " + entry + " in manifest");
895   }
896
897   /**
898    * A utility class that verifies jar entries as they are read.
899    */
900   private static class EntryInputStream extends FilterInputStream
901   {
902     private final JarFile jarfile;
903     private final long length;
904     private long pos;
905     private final ZipEntry entry;
906     private final byte[][] hashes;
907     private final MessageDigest[] md;
908     private boolean checked;
909
910     EntryInputStream(final ZipEntry entry,
911                      final InputStream in,
912                      final JarFile jar)
913       throws IOException
914     {
915       super(in);
916       this.entry = entry;
917       this.jarfile = jar;
918
919       length = entry.getSize();
920       pos = 0;
921       checked = false;
922
923       Attributes attr;
924       Manifest manifest = jarfile.getManifest();
925       if (manifest != null)
926         attr = manifest.getAttributes(entry.getName());
927       else
928         attr = null;
929       if (DEBUG)
930         debug("verifying entry " + entry + " attr=" + attr);
931       if (attr == null)
932         {
933           hashes = new byte[0][];
934           md = new MessageDigest[0];
935         }
936       else
937         {
938           List hashes = new LinkedList();
939           List md = new LinkedList();
940           for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
941             {
942               Map.Entry e = (Map.Entry) it.next();
943               String key = String.valueOf(e.getKey());
944               if (key == null)
945                 continue;
946               if (!key.endsWith(DIGEST_KEY_SUFFIX))
947                 continue;
948               hashes.add(Base64InputStream.decode((String) e.getValue()));
949               try
950                 {
951                   int length = key.length() - DIGEST_KEY_SUFFIX.length();
952                   String alg = key.substring(0, length);
953                   md.add(MessageDigest.getInstance(alg, provider));
954                 }
955               catch (NoSuchAlgorithmException nsae)
956                 {
957                   IOException ioe = new IOException("no such message digest: " + key);
958                   ioe.initCause(nsae);
959                   throw ioe;
960                 }
961             }
962           if (DEBUG)
963             debug("digests=" + md);
964           this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]);
965           this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]);
966         }
967     }
968
969     public boolean markSupported()
970     {
971       return false;
972     }
973
974     public void mark(int readLimit)
975     {
976     }
977
978     public void reset()
979     {
980     }
981
982     public int read() throws IOException
983     {
984       int b = super.read();
985       if (b == -1)
986         {
987           eof();
988           return -1;
989         }
990       for (int i = 0; i < md.length; i++)
991         md[i].update((byte) b);
992       pos++;
993       if (length > 0 && pos >= length)
994         eof();
995       return b;
996     }
997
998     public int read(byte[] buf, int off, int len) throws IOException
999     {
1000       int count = super.read(buf, off, (int) Math.min(len, (length != 0
1001                                                             ? length - pos
1002                                                             : Integer.MAX_VALUE)));
1003       if (count == -1 || (length > 0 && pos >= length))
1004         {
1005           eof();
1006           return -1;
1007         }
1008       for (int i = 0; i < md.length; i++)
1009         md[i].update(buf, off, count);
1010       pos += count;
1011       if (length != 0 && pos >= length)
1012         eof();
1013       return count;
1014     }
1015
1016     public int read(byte[] buf) throws IOException
1017     {
1018       return read(buf, 0, buf.length);
1019     }
1020
1021     public long skip(long bytes) throws IOException
1022     {
1023       byte[] b = new byte[1024];
1024       long amount = 0;
1025       while (amount < bytes)
1026         {
1027           int l = read(b, 0, (int) Math.min(b.length, bytes - amount));
1028           if (l == -1)
1029             break;
1030           amount += l;
1031         }
1032       return amount;
1033     }
1034
1035     private void eof() throws IOException
1036     {
1037       if (checked)
1038         return;
1039       checked = true;
1040       for (int i = 0; i < md.length; i++)
1041         {
1042           byte[] hash = md[i].digest();
1043           if (DEBUG)
1044             debug("verifying " + md[i].getAlgorithm() + " expect="
1045                   + new java.math.BigInteger(hashes[i]).toString(16)
1046                   + " comp=" + new java.math.BigInteger(hash).toString(16));
1047           if (!Arrays.equals(hash, hashes[i]))
1048             {
1049               synchronized(jarfile)
1050                 {
1051                   if (DEBUG)
1052                     debug(entry + " could NOT be verified");
1053                   jarfile.verified.put(entry.getName(), Boolean.FALSE);
1054                 }
1055               return;
1056               // XXX ??? what do we do here?
1057               // throw new ZipException("message digest mismatch");
1058             }
1059         }
1060
1061       synchronized(jarfile)
1062         {
1063           if (DEBUG)
1064             debug(entry + " has been VERIFIED");
1065           jarfile.verified.put(entry.getName(), Boolean.TRUE);
1066         }
1067     }
1068   }
1069 }