Dead
[official-gcc.git] / gomp-20050608-branch / libjava / classpath / java / util / jar / JarFile.java
blob7ccbc60af87bf1eaef1ecbb1c84728b8cd655ba7
1 /* JarFile.java - Representation of a jar file
2 Copyright (C) 2000, 2003, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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.
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.
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.
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.
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. */
39 package java.util.jar;
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;
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;
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).
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.
86 public class JarFile extends ZipFile
88 // Fields
90 /** The name of the manifest entry: META-INF/MANIFEST.MF */
91 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
93 /** The META-INF directory entry. */
94 private static final String META_INF = "META-INF/";
96 /** The suffix for PKCS7 DSA signature entries. */
97 private static final String PKCS7_DSA_SUFFIX = ".DSA";
99 /** The suffix for PKCS7 RSA signature entries. */
100 private static final String PKCS7_RSA_SUFFIX = ".RSA";
102 /** The suffix for digest attributes. */
103 private static final String DIGEST_KEY_SUFFIX = "-Digest";
105 /** The suffix for signature files. */
106 private static final String SF_SUFFIX = ".SF";
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");
117 * The manifest of this file, if any, otherwise null.
118 * Read when first needed.
120 private Manifest manifest;
122 /** Whether to verify the manifest and all entries. */
123 boolean verify;
125 /** Whether the has already been loaded. */
126 private boolean manifestRead = false;
128 /** Whether the signature files have been loaded. */
129 boolean signaturesRead = false;
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();
138 * A mapping from entry name to certificates, if any.
139 * Only accessed with lock on this JarFile.
141 HashMap entryCerts;
143 static boolean DEBUG = false;
144 static void debug(Object msg)
146 System.err.print(JarFile.class.getName());
147 System.err.print(" >>> ");
148 System.err.println(msg);
151 // Constructors
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.
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
162 public JarFile(String fileName) throws FileNotFoundException, IOException
164 this(fileName, true);
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.
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
179 public JarFile(String fileName, boolean verify) throws
180 FileNotFoundException, IOException
182 super(fileName);
183 if (verify)
185 manifest = readManifest();
186 verify();
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.
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
199 public JarFile(File file) throws FileNotFoundException, IOException
201 this(file, true);
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.
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
216 public JarFile(File file, boolean verify) throws FileNotFoundException,
217 IOException
219 super(file);
220 if (verify)
222 manifest = readManifest();
223 verify();
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.
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
243 * @since 1.3
245 public JarFile(File file, boolean verify, int mode) throws
246 FileNotFoundException, IOException, IllegalArgumentException
248 super(file, mode);
249 if (verify)
251 manifest = readManifest();
252 verify();
256 // Methods
259 * XXX - should verify the manifest file
261 private void verify()
263 // only check if manifest is not null
264 if (manifest == null)
266 verify = false;
267 return;
270 verify = true;
271 // XXX - verify manifest
275 * Parses and returns the manifest if it exists, otherwise returns null.
277 private Manifest readManifest()
281 ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
282 if (manEntry != null)
284 InputStream in = super.getInputStream(manEntry);
285 manifestRead = true;
286 return new Manifest(in);
288 else
290 manifestRead = true;
291 return null;
294 catch (IOException ioe)
296 manifestRead = true;
297 return null;
302 * Returns a enumeration of all the entries in the JarFile.
303 * Note that also the Jar META-INF entries are returned.
305 * @exception IllegalStateException when the JarFile is already closed
307 public Enumeration entries() throws IllegalStateException
309 return new JarEnumeration(super.entries(), this);
313 * Wraps a given Zip Entries Enumeration. For every zip entry a
314 * JarEntry is created and the corresponding Attributes are looked up.
316 private static class JarEnumeration implements Enumeration
319 private final Enumeration entries;
320 private final JarFile jarfile;
322 JarEnumeration(Enumeration e, JarFile f)
324 entries = e;
325 jarfile = f;
328 public boolean hasMoreElements()
330 return entries.hasMoreElements();
333 public Object nextElement()
335 ZipEntry zip = (ZipEntry) entries.nextElement();
336 JarEntry jar = new JarEntry(zip);
337 Manifest manifest;
340 manifest = jarfile.getManifest();
342 catch (IOException ioe)
344 manifest = null;
347 if (manifest != null)
349 jar.attr = manifest.getAttributes(jar.getName());
352 synchronized(jarfile)
354 if (jarfile.verify && !jarfile.signaturesRead)
357 jarfile.readSignatures();
359 catch (IOException ioe)
361 if (JarFile.DEBUG)
363 JarFile.debug(ioe);
364 ioe.printStackTrace();
366 jarfile.signaturesRead = true; // fudge it.
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)
375 Set certs = (Set) jarfile.entryCerts.get(jar.getName());
376 if (certs != null)
377 jar.certs = (Certificate[])
378 certs.toArray(new Certificate[certs.size()]);
381 return jar;
386 * XXX
387 * It actually returns a JarEntry not a zipEntry
388 * @param name XXX
390 public synchronized ZipEntry getEntry(String name)
392 ZipEntry entry = super.getEntry(name);
393 if (entry != null)
395 JarEntry jarEntry = new JarEntry(entry);
396 Manifest manifest;
399 manifest = getManifest();
401 catch (IOException ioe)
403 manifest = null;
406 if (manifest != null)
408 jarEntry.attr = manifest.getAttributes(name);
411 if (verify && !signaturesRead)
414 readSignatures();
416 catch (IOException ioe)
418 if (DEBUG)
420 debug(ioe);
421 ioe.printStackTrace();
423 signaturesRead = true;
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)
432 Set certs = (Set) entryCerts.get(name);
433 if (certs != null)
434 jarEntry.certs = (Certificate[])
435 certs.toArray(new Certificate[certs.size()]);
437 return jarEntry;
439 return null;
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.
447 * @param entry The entry to get the input stream for.
448 * @exception ZipException XXX
449 * @exception IOException XXX
451 public synchronized InputStream getInputStream(ZipEntry entry) throws
452 ZipException, IOException
454 // If we haven't verified the hash, do it now.
455 if (!verified.containsKey(entry.getName()) && verify)
457 if (DEBUG)
458 debug("reading and verifying " + entry);
459 return new EntryInputStream(entry, super.getInputStream(entry), this);
461 else
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);
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.
477 * @param name the jar entry name to look up
478 * @return the JarEntry if it exists, null otherwise
480 public JarEntry getJarEntry(String name)
482 return (JarEntry) getEntry(name);
486 * Returns the manifest for this JarFile or null when the JarFile does not
487 * contain a manifest file.
489 public synchronized Manifest getManifest() throws IOException
491 if (!manifestRead)
492 manifest = readManifest();
494 return manifest;
497 // Only called with lock on this JarFile.
498 // Package private for use in inner classes.
499 void readSignatures() throws IOException
501 Map pkcs7Dsa = new HashMap();
502 Map pkcs7Rsa = new HashMap();
503 Map sigFiles = new HashMap();
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(); )
509 ZipEntry ze = (ZipEntry) e.nextElement();
510 String name = ze.getName();
511 if (name.startsWith(META_INF))
513 String alias = name.substring(META_INF.length());
514 if (alias.lastIndexOf('.') >= 0)
515 alias = alias.substring(0, alias.lastIndexOf('.'));
517 if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX))
519 if (DEBUG)
520 debug("reading PKCS7 info from " + name + ", alias=" + alias);
521 PKCS7SignedData sig = null;
524 sig = new PKCS7SignedData(super.getInputStream(ze));
526 catch (CertificateException ce)
528 IOException ioe = new IOException("certificate parsing error");
529 ioe.initCause(ce);
530 throw ioe;
532 catch (CRLException crle)
534 IOException ioe = new IOException("CRL parsing error");
535 ioe.initCause(crle);
536 throw ioe;
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);
543 else if (name.endsWith(SF_SUFFIX))
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);
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(); )
560 int valid = 0;
561 Map.Entry e = (Map.Entry) it.next();
562 String alias = (String) e.getKey();
564 PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias);
565 if (sig != null)
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);
573 sig = (PKCS7SignedData) pkcs7Rsa.get(alias);
574 if (sig != null)
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);
582 // It isn't a signature for anything. Punt it.
583 if (validCerts.isEmpty())
585 it.remove();
586 continue;
589 entryCerts.put(e.getValue(), new HashSet(validCerts));
590 validCerts.clear();
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(); )
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();
603 for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); )
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))
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));
621 signaturesRead = true;
625 * Tell if the given signer info is over the given alias's signature file,
626 * given one of the certificates specified.
628 private void verify(Certificate[] certs, SignerInfo signerInfo,
629 String alias, Set validCerts)
631 Signature sig = null;
634 OID alg = signerInfo.getDigestEncryptionAlgorithmId();
635 if (alg.equals(DSA_ENCRYPTION_OID))
637 if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID))
638 return;
639 sig = Signature.getInstance("SHA1withDSA");
641 else if (alg.equals(RSA_ENCRYPTION_OID))
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;
655 else
657 if (DEBUG)
658 debug("unsupported signature algorithm: " + alg);
659 return;
662 catch (NoSuchAlgorithmException nsae)
664 if (DEBUG)
666 debug(nsae);
667 nsae.printStackTrace();
669 return;
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++)
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;
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()))
694 if (DEBUG)
695 debug("signature for " + cert.getSubjectDN() + " is good");
696 validCerts.add(cert);
699 catch (IOException ioe)
701 continue;
703 catch (InvalidKeyException ike)
705 continue;
707 catch (SignatureException se)
709 continue;
715 * Verifies that the digest(s) in a signature file were, in fact, made
716 * over the manifest entry for ENTRY.
718 * @param entry The entry name.
719 * @param attr The attributes from the signature file to verify.
721 private boolean verifyHashes(String entry, Attributes attr)
723 int verified = 0;
725 // The bytes for ENTRY's manifest entry, which are signed in the
726 // signature file.
727 byte[] entryBytes = null;
730 ZipEntry e = super.getEntry(entry);
731 if (e == null)
733 if (DEBUG)
734 debug("verifyHashes: no entry '" + entry + "'");
735 return false;
737 entryBytes = readManifestEntry(e);
739 catch (IOException ioe)
741 if (DEBUG)
743 debug(ioe);
744 ioe.printStackTrace();
746 return false;
749 for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
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());
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++;
770 catch (IOException ioe)
772 if (DEBUG)
774 debug(ioe);
775 ioe.printStackTrace();
777 return false;
779 catch (NoSuchAlgorithmException nsae)
781 if (DEBUG)
783 debug(nsae);
784 nsae.printStackTrace();
786 return false;
790 // We have to find at least one valid digest.
791 return verified > 0;
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).
799 private byte[] readManifestEntry(ZipEntry entry) throws IOException
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;
806 while ((c = in.read()) != -1)
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)
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
825 t++;
826 if (t == target.length)
828 out.write(target);
829 state = 1;
832 break;
834 // Step 2: assert that there is a newline character after
835 // the "target" bytes.
836 case 1:
837 if (c != '\n' && c != '\r')
839 out.reset();
840 t = 0;
841 state = 0;
843 else
845 out.write(c);
846 state = 2;
848 break;
850 // Step 3: read this whole entry, until we reach an empty
851 // line.
852 case 2:
853 if (c == '\n')
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;
861 else
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++;
871 prev = c;
872 break;
874 default:
875 throw new RuntimeException("this statement should be unreachable");
879 // The last entry, with a single CR terminating the line.
880 if (state == 2 && prev == '\r' && l == 0)
881 return out.toByteArray();
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");
889 * A utility class that verifies jar entries as they are read.
891 private static class EntryInputStream extends FilterInputStream
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;
901 EntryInputStream(final ZipEntry entry,
902 final InputStream in,
903 final JarFile jar)
904 throws IOException
906 super(in);
907 this.entry = entry;
908 this.jarfile = jar;
910 length = entry.getSize();
911 pos = 0;
912 checked = false;
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)
924 hashes = new byte[0][];
925 md = new MessageDigest[0];
927 else
929 List hashes = new LinkedList();
930 List md = new LinkedList();
931 for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
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()));
942 md.add(MessageDigest.getInstance
943 (key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length())));
945 catch (NoSuchAlgorithmException nsae)
947 IOException ioe = new IOException("no such message digest: " + key);
948 ioe.initCause(nsae);
949 throw ioe;
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()]);
959 public boolean markSupported()
961 return false;
964 public void mark(int readLimit)
968 public void reset()
972 public int read() throws IOException
974 int b = super.read();
975 if (b == -1)
977 eof();
978 return -1;
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;
988 public int read(byte[] buf, int off, int len) throws IOException
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))
995 eof();
996 return -1;
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;
1006 public int read(byte[] buf) throws IOException
1008 return read(buf, 0, buf.length);
1011 public long skip(long bytes) throws IOException
1013 byte[] b = new byte[1024];
1014 long amount = 0;
1015 while (amount < bytes)
1017 int l = read(b, 0, (int) Math.min(b.length, bytes - amount));
1018 if (l == -1)
1019 break;
1020 amount += l;
1022 return amount;
1025 private void eof() throws IOException
1027 if (checked)
1028 return;
1029 checked = true;
1030 for (int i = 0; i < md.length; i++)
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]))
1039 synchronized(jarfile)
1041 if (DEBUG)
1042 debug(entry + " could NOT be verified");
1043 jarfile.verified.put(entry.getName(), Boolean.FALSE);
1045 return;
1046 // XXX ??? what do we do here?
1047 // throw new ZipException("message digest mismatch");
1051 synchronized(jarfile)
1053 if (DEBUG)
1054 debug(entry + " has been VERIFIED");
1055 jarfile.verified.put(entry.getName(), Boolean.TRUE);