Source for java.util.jar.JarFile

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