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