1 /* JarFile.java - Representation of a jar file
2 Copyright (C) 2000, 2003, 2004, 2005, 2006 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)
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
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
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;
45 import gnu.java.security.provider.Gnu;
47 import java.io.ByteArrayOutputStream;
49 import java.io.FileNotFoundException;
50 import java.io.FilterInputStream;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.security.InvalidKeyException;
54 import java.security.MessageDigest;
55 import java.security.NoSuchAlgorithmException;
56 import java.security.Signature;
57 import java.security.SignatureException;
58 import java.security.cert.CRLException;
59 import java.security.cert.Certificate;
60 import java.security.cert.CertificateException;
61 import java.security.cert.X509Certificate;
62 import java.util.Arrays;
63 import java.util.Enumeration;
64 import java.util.HashMap;
65 import java.util.HashSet;
66 import java.util.Iterator;
67 import java.util.LinkedList;
68 import java.util.List;
71 import java.util.zip.ZipEntry;
72 import java.util.zip.ZipException;
73 import java.util.zip.ZipFile;
76 * Representation of a jar file.
78 * Note that this class is not a subclass of java.io.File but a subclass of
79 * java.util.zip.ZipFile and you can only read JarFiles with it (although
80 * there are constructors that take a File object).
83 * @author Mark Wielaard (mark@klomp.org)
84 * @author Casey Marshall (csm@gnu.org) wrote the certificate and entry
87 public class JarFile extends ZipFile
91 /** The name of the manifest entry: META-INF/MANIFEST.MF */
92 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
94 /** The META-INF directory entry. */
95 private static final String META_INF = "META-INF/";
97 /** The suffix for PKCS7 DSA signature entries. */
98 private static final String PKCS7_DSA_SUFFIX = ".DSA";
100 /** The suffix for PKCS7 RSA signature entries. */
101 private static final String PKCS7_RSA_SUFFIX = ".RSA";
103 /** The suffix for digest attributes. */
104 private static final String DIGEST_KEY_SUFFIX = "-Digest";
106 /** The suffix for signature files. */
107 private static final String SF_SUFFIX = ".SF";
110 * The security provider to use for signature verification.
111 * We need a known fallback to be able to read any signed jar file
112 * (which might contain the user selected security provider).
113 * This is package-private to avoid accessor methods for inner classes.
115 static final Gnu provider = new Gnu();
118 private static final OID MD2_OID = new OID("1.2.840.113549.2.2");
119 private static final OID MD4_OID = new OID("1.2.840.113549.2.4");
120 private static final OID MD5_OID = new OID("1.2.840.113549.2.5");
121 private static final OID SHA1_OID = new OID("1.3.14.3.2.26");
122 private static final OID DSA_ENCRYPTION_OID = new OID("1.2.840.10040.4.1");
123 private static final OID RSA_ENCRYPTION_OID = new OID("1.2.840.113549.1.1.1");
126 * The manifest of this file, if any, otherwise null.
127 * Read when first needed.
129 private Manifest manifest;
131 /** Whether to verify the manifest and all entries. */
134 /** Whether the has already been loaded. */
135 private boolean manifestRead = false;
137 /** Whether the signature files have been loaded. */
138 boolean signaturesRead = false;
141 * A map between entry names and booleans, signaling whether or
142 * not that entry has been verified.
143 * Only be accessed with lock on this JarFile*/
144 HashMap verified = new HashMap();
147 * A mapping from entry name to certificates, if any.
148 * Only accessed with lock on this JarFile.
152 static boolean DEBUG = false;
153 static void debug(Object msg)
155 System.err.print(JarFile.class.getName());
156 System.err.print(" >>> ");
157 System.err.println(msg);
163 * Creates a new JarFile. All jar entries are verified (when a Manifest file
164 * for this JarFile exists). You need to actually open and read the complete
165 * jar entry (with <code>getInputStream()</code>) to check its signature.
167 * @param fileName the name of the file to open
168 * @exception FileNotFoundException if the fileName cannot be found
169 * @exception IOException if another IO exception occurs while reading
171 public JarFile(String fileName) throws FileNotFoundException, IOException
173 this(fileName, true);
177 * Creates a new JarFile. If verify is true then all jar entries are
178 * verified (when a Manifest file for this JarFile exists). You need to
179 * actually open and read the complete jar entry
180 * (with <code>getInputStream()</code>) to check its signature.
182 * @param fileName the name of the file to open
183 * @param verify checks manifest and entries when true and a manifest
184 * exists, when false no checks are made
185 * @exception FileNotFoundException if the fileName cannot be found
186 * @exception IOException if another IO exception occurs while reading
188 public JarFile(String fileName, boolean verify) throws
189 FileNotFoundException, IOException
194 manifest = readManifest();
200 * Creates a new JarFile. All jar entries are verified (when a Manifest file
201 * for this JarFile exists). You need to actually open and read the complete
202 * jar entry (with <code>getInputStream()</code>) to check its signature.
204 * @param file the file to open as a jar file
205 * @exception FileNotFoundException if the file does not exits
206 * @exception IOException if another IO exception occurs while reading
208 public JarFile(File file) throws FileNotFoundException, IOException
214 * Creates a new JarFile. If verify is true then all jar entries are
215 * verified (when a Manifest file for this JarFile exists). You need to
216 * actually open and read the complete jar entry
217 * (with <code>getInputStream()</code>) to check its signature.
219 * @param file the file to open to open as a jar file
220 * @param verify checks manifest and entries when true and a manifest
221 * exists, when false no checks are made
222 * @exception FileNotFoundException if file does not exist
223 * @exception IOException if another IO exception occurs while reading
225 public JarFile(File file, boolean verify) throws FileNotFoundException,
231 manifest = readManifest();
237 * Creates a new JarFile with the indicated mode. If verify is true then
238 * all jar entries are verified (when a Manifest file for this JarFile
239 * exists). You need to actually open and read the complete jar entry
240 * (with <code>getInputStream()</code>) to check its signature.
241 * manifest and if the manifest exists and verify is true verfies it.
243 * @param file the file to open to open as a jar file
244 * @param verify checks manifest and entries when true and a manifest
245 * exists, when false no checks are made
246 * @param mode either ZipFile.OPEN_READ or
247 * (ZipFile.OPEN_READ | ZipFile.OPEN_DELETE)
248 * @exception FileNotFoundException if the file does not exist
249 * @exception IOException if another IO exception occurs while reading
250 * @exception IllegalArgumentException when given an illegal mode
254 public JarFile(File file, boolean verify, int mode) throws
255 FileNotFoundException, IOException, IllegalArgumentException
260 manifest = readManifest();
268 * XXX - should verify the manifest file
270 private void verify()
272 // only check if manifest is not null
273 if (manifest == null)
280 // XXX - verify manifest
284 * Parses and returns the manifest if it exists, otherwise returns null.
286 private Manifest readManifest()
290 ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
291 if (manEntry != null)
293 InputStream in = super.getInputStream(manEntry);
295 return new Manifest(in);
303 catch (IOException ioe)
311 * Returns a enumeration of all the entries in the JarFile.
312 * Note that also the Jar META-INF entries are returned.
314 * @exception IllegalStateException when the JarFile is already closed
316 public Enumeration entries() throws IllegalStateException
318 return new JarEnumeration(super.entries(), this);
322 * Wraps a given Zip Entries Enumeration. For every zip entry a
323 * JarEntry is created and the corresponding Attributes are looked up.
325 private static class JarEnumeration implements Enumeration
328 private final Enumeration entries;
329 private final JarFile jarfile;
331 JarEnumeration(Enumeration e, JarFile f)
337 public boolean hasMoreElements()
339 return entries.hasMoreElements();
342 public Object nextElement()
344 ZipEntry zip = (ZipEntry) entries.nextElement();
345 JarEntry jar = new JarEntry(zip);
349 manifest = jarfile.getManifest();
351 catch (IOException ioe)
356 if (manifest != null)
358 jar.attr = manifest.getAttributes(jar.getName());
361 synchronized(jarfile)
363 if (jarfile.verify && !jarfile.signaturesRead)
366 jarfile.readSignatures();
368 catch (IOException ioe)
373 ioe.printStackTrace();
375 jarfile.signaturesRead = true; // fudge it.
378 // Include the certificates only if we have asserted that the
379 // signatures are valid. This means the certificates will not be
380 // available if the entry hasn't been read yet.
381 if (jarfile.entryCerts != null
382 && jarfile.verified.get(zip.getName()) == Boolean.TRUE)
384 Set certs = (Set) jarfile.entryCerts.get(jar.getName());
386 jar.certs = (Certificate[])
387 certs.toArray(new Certificate[certs.size()]);
396 * It actually returns a JarEntry not a zipEntry
399 public synchronized ZipEntry getEntry(String name)
401 ZipEntry entry = super.getEntry(name);
404 JarEntry jarEntry = new JarEntry(entry);
408 manifest = getManifest();
410 catch (IOException ioe)
415 if (manifest != null)
417 jarEntry.attr = manifest.getAttributes(name);
420 if (verify && !signaturesRead)
425 catch (IOException ioe)
430 ioe.printStackTrace();
432 signaturesRead = true;
434 // See the comments in the JarEnumeration for why we do this
437 debug("entryCerts=" + entryCerts + " verified " + name
438 + " ? " + verified.get(name));
439 if (entryCerts != null && verified.get(name) == Boolean.TRUE)
441 Set certs = (Set) entryCerts.get(name);
443 jarEntry.certs = (Certificate[])
444 certs.toArray(new Certificate[certs.size()]);
452 * Returns an input stream for the given entry. If configured to
453 * verify entries, the input stream returned will verify them while
454 * the stream is read, but only on the first time.
456 * @param entry The entry to get the input stream for.
457 * @exception ZipException XXX
458 * @exception IOException XXX
460 public synchronized InputStream getInputStream(ZipEntry entry) throws
461 ZipException, IOException
463 // If we haven't verified the hash, do it now.
464 if (!verified.containsKey(entry.getName()) && verify)
467 debug("reading and verifying " + entry);
468 return new EntryInputStream(entry, super.getInputStream(entry), this);
473 debug("reading already verified entry " + entry);
474 if (verify && verified.get(entry.getName()) == Boolean.FALSE)
475 throw new ZipException("digest for " + entry + " is invalid");
476 return super.getInputStream(entry);
481 * Returns the JarEntry that belongs to the name if such an entry
482 * exists in the JarFile. Returns null otherwise
483 * Convenience method that just casts the result from <code>getEntry</code>
486 * @param name the jar entry name to look up
487 * @return the JarEntry if it exists, null otherwise
489 public JarEntry getJarEntry(String name)
491 return (JarEntry) getEntry(name);
495 * Returns the manifest for this JarFile or null when the JarFile does not
496 * contain a manifest file.
498 public synchronized Manifest getManifest() throws IOException
501 manifest = readManifest();
506 // Only called with lock on this JarFile.
507 // Package private for use in inner classes.
508 void readSignatures() throws IOException
510 Map pkcs7Dsa = new HashMap();
511 Map pkcs7Rsa = new HashMap();
512 Map sigFiles = new HashMap();
514 // Phase 1: Read all signature files. These contain the user
515 // certificates as well as the signatures themselves.
516 for (Enumeration e = super.entries(); e.hasMoreElements(); )
518 ZipEntry ze = (ZipEntry) e.nextElement();
519 String name = ze.getName();
520 if (name.startsWith(META_INF))
522 String alias = name.substring(META_INF.length());
523 if (alias.lastIndexOf('.') >= 0)
524 alias = alias.substring(0, alias.lastIndexOf('.'));
526 if (name.endsWith(PKCS7_DSA_SUFFIX) || name.endsWith(PKCS7_RSA_SUFFIX))
529 debug("reading PKCS7 info from " + name + ", alias=" + alias);
530 PKCS7SignedData sig = null;
533 sig = new PKCS7SignedData(super.getInputStream(ze));
535 catch (CertificateException ce)
537 IOException ioe = new IOException("certificate parsing error");
541 catch (CRLException crle)
543 IOException ioe = new IOException("CRL parsing error");
547 if (name.endsWith(PKCS7_DSA_SUFFIX))
548 pkcs7Dsa.put(alias, sig);
549 else if (name.endsWith(PKCS7_RSA_SUFFIX))
550 pkcs7Rsa.put(alias, sig);
552 else if (name.endsWith(SF_SUFFIX))
555 debug("reading signature file for " + alias + ": " + name);
556 Manifest sf = new Manifest(super.getInputStream(ze));
557 sigFiles.put(alias, sf);
559 debug("result: " + sf);
564 // Phase 2: verify the signatures on any signature files.
565 Set validCerts = new HashSet();
566 Map entryCerts = new HashMap();
567 for (Iterator it = sigFiles.entrySet().iterator(); it.hasNext(); )
570 Map.Entry e = (Map.Entry) it.next();
571 String alias = (String) e.getKey();
573 PKCS7SignedData sig = (PKCS7SignedData) pkcs7Dsa.get(alias);
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 sig = (PKCS7SignedData) pkcs7Rsa.get(alias);
585 Certificate[] certs = sig.getCertificates();
586 Set signerInfos = sig.getSignerInfos();
587 for (Iterator it2 = signerInfos.iterator(); it2.hasNext(); )
588 verify(certs, (SignerInfo) it2.next(), alias, validCerts);
591 // It isn't a signature for anything. Punt it.
592 if (validCerts.isEmpty())
598 entryCerts.put(e.getValue(), new HashSet(validCerts));
602 // Phase 3: verify the signature file signatures against the manifest,
603 // mapping the entry name to the target certificates.
604 this.entryCerts = new HashMap();
605 for (Iterator it = entryCerts.entrySet().iterator(); it.hasNext(); )
607 Map.Entry e = (Map.Entry) it.next();
608 Manifest sigfile = (Manifest) e.getKey();
609 Map entries = sigfile.getEntries();
610 Set certificates = (Set) e.getValue();
612 for (Iterator it2 = entries.entrySet().iterator(); it2.hasNext(); )
614 Map.Entry e2 = (Map.Entry) it2.next();
615 String entryname = String.valueOf(e2.getKey());
616 Attributes attr = (Attributes) e2.getValue();
617 if (verifyHashes(entryname, attr))
620 debug("entry " + entryname + " has certificates " + certificates);
621 Set s = (Set) this.entryCerts.get(entryname);
623 s.addAll(certificates);
625 this.entryCerts.put(entryname, new HashSet(certificates));
630 signaturesRead = true;
634 * Tell if the given signer info is over the given alias's signature file,
635 * given one of the certificates specified.
637 private void verify(Certificate[] certs, SignerInfo signerInfo,
638 String alias, Set validCerts)
640 Signature sig = null;
643 OID alg = signerInfo.getDigestEncryptionAlgorithmId();
644 if (alg.equals(DSA_ENCRYPTION_OID))
646 if (!signerInfo.getDigestAlgorithmId().equals(SHA1_OID))
648 sig = Signature.getInstance("SHA1withDSA", provider);
650 else if (alg.equals(RSA_ENCRYPTION_OID))
652 OID hash = signerInfo.getDigestAlgorithmId();
653 if (hash.equals(MD2_OID))
654 sig = Signature.getInstance("md2WithRsaEncryption", provider);
655 else if (hash.equals(MD4_OID))
656 sig = Signature.getInstance("md4WithRsaEncryption", provider);
657 else if (hash.equals(MD5_OID))
658 sig = Signature.getInstance("md5WithRsaEncryption", provider);
659 else if (hash.equals(SHA1_OID))
660 sig = Signature.getInstance("sha1WithRsaEncryption", provider);
667 debug("unsupported signature algorithm: " + alg);
671 catch (NoSuchAlgorithmException nsae)
676 nsae.printStackTrace();
680 ZipEntry sigFileEntry = super.getEntry(META_INF + alias + SF_SUFFIX);
681 if (sigFileEntry == null)
683 for (int i = 0; i < certs.length; i++)
685 if (!(certs[i] instanceof X509Certificate))
687 X509Certificate cert = (X509Certificate) certs[i];
688 if (!cert.getIssuerX500Principal().equals(signerInfo.getIssuer()) ||
689 !cert.getSerialNumber().equals(signerInfo.getSerialNumber()))
693 sig.initVerify(cert.getPublicKey());
694 InputStream in = super.getInputStream(sigFileEntry);
697 byte[] buf = new byte[1024];
699 while ((len = in.read(buf)) != -1)
700 sig.update(buf, 0, len);
701 if (sig.verify(signerInfo.getEncryptedDigest()))
704 debug("signature for " + cert.getSubjectDN() + " is good");
705 validCerts.add(cert);
708 catch (IOException ioe)
712 catch (InvalidKeyException ike)
716 catch (SignatureException se)
724 * Verifies that the digest(s) in a signature file were, in fact, made
725 * over the manifest entry for ENTRY.
727 * @param entry The entry name.
728 * @param attr The attributes from the signature file to verify.
730 private boolean verifyHashes(String entry, Attributes attr)
734 // The bytes for ENTRY's manifest entry, which are signed in the
736 byte[] entryBytes = null;
739 ZipEntry e = super.getEntry(entry);
743 debug("verifyHashes: no entry '" + entry + "'");
746 entryBytes = readManifestEntry(e);
748 catch (IOException ioe)
753 ioe.printStackTrace();
758 for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
760 Map.Entry e = (Map.Entry) it.next();
761 String key = String.valueOf(e.getKey());
762 if (!key.endsWith(DIGEST_KEY_SUFFIX))
764 String alg = key.substring(0, key.length() - DIGEST_KEY_SUFFIX.length());
767 byte[] hash = Base64InputStream.decode((String) e.getValue());
768 MessageDigest md = MessageDigest.getInstance(alg, provider);
769 md.update(entryBytes);
770 byte[] hash2 = md.digest();
772 debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm()
773 + " expect=" + new java.math.BigInteger(hash).toString(16)
774 + " comp=" + new java.math.BigInteger(hash2).toString(16));
775 if (!Arrays.equals(hash, hash2))
779 catch (IOException ioe)
784 ioe.printStackTrace();
788 catch (NoSuchAlgorithmException nsae)
793 nsae.printStackTrace();
799 // We have to find at least one valid digest.
804 * Read the raw bytes that comprise a manifest entry. We can't use the
805 * Manifest object itself, because that loses information (such as line
806 * endings, and order of entries).
808 private byte[] readManifestEntry(ZipEntry entry) throws IOException
810 InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
811 ByteArrayOutputStream out = new ByteArrayOutputStream();
812 byte[] target = ("Name: " + entry.getName()).getBytes();
813 int t = 0, c, prev = -1, state = 0, l = -1;
815 while ((c = in.read()) != -1)
819 // + (c == '\n' ? "\\n" : (c == '\r' ? "\\r" : String.valueOf((char) c)))
820 // + " state=" + state + " prev="
821 // + (prev == '\n' ? "\\n" : (prev == '\r' ? "\\r" : String.valueOf((char) prev)))
822 // + " t=" + t + (t < target.length ? (" target[t]=" + (char) target[t]) : "")
827 // Step 1: read until we find the "target" bytes: the start
828 // of the entry we need to read.
830 if (((byte) c) != target[t])
835 if (t == target.length)
843 // Step 2: assert that there is a newline character after
844 // the "target" bytes.
846 if (c != '\n' && c != '\r')
859 // Step 3: read this whole entry, until we reach an empty
865 // NL always terminates a line.
866 if (l == 0 || (l == 1 && prev == '\r'))
867 return out.toByteArray();
872 // Here we see a blank line terminated by a CR,
873 // followed by the next entry. Technically, `c' should
874 // always be 'N' at this point.
875 if (l == 1 && prev == '\r')
876 return out.toByteArray();
884 throw new RuntimeException("this statement should be unreachable");
888 // The last entry, with a single CR terminating the line.
889 if (state == 2 && prev == '\r' && l == 0)
890 return out.toByteArray();
892 // We should not reach this point, we didn't find the entry (or, possibly,
893 // it is the last entry and is malformed).
894 throw new IOException("could not find " + entry + " in manifest");
898 * A utility class that verifies jar entries as they are read.
900 private static class EntryInputStream extends FilterInputStream
902 private final JarFile jarfile;
903 private final long length;
905 private final ZipEntry entry;
906 private final byte[][] hashes;
907 private final MessageDigest[] md;
908 private boolean checked;
910 EntryInputStream(final ZipEntry entry,
911 final InputStream in,
919 length = entry.getSize();
924 Manifest manifest = jarfile.getManifest();
925 if (manifest != null)
926 attr = manifest.getAttributes(entry.getName());
930 debug("verifying entry " + entry + " attr=" + attr);
933 hashes = new byte[0][];
934 md = new MessageDigest[0];
938 List hashes = new LinkedList();
939 List md = new LinkedList();
940 for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
942 Map.Entry e = (Map.Entry) it.next();
943 String key = String.valueOf(e.getKey());
946 if (!key.endsWith(DIGEST_KEY_SUFFIX))
948 hashes.add(Base64InputStream.decode((String) e.getValue()));
951 int length = key.length() - DIGEST_KEY_SUFFIX.length();
952 String alg = key.substring(0, length);
953 md.add(MessageDigest.getInstance(alg, provider));
955 catch (NoSuchAlgorithmException nsae)
957 IOException ioe = new IOException("no such message digest: " + key);
963 debug("digests=" + md);
964 this.hashes = (byte[][]) hashes.toArray(new byte[hashes.size()][]);
965 this.md = (MessageDigest[]) md.toArray(new MessageDigest[md.size()]);
969 public boolean markSupported()
974 public void mark(int readLimit)
982 public int read() throws IOException
984 int b = super.read();
990 for (int i = 0; i < md.length; i++)
991 md[i].update((byte) b);
993 if (length > 0 && pos >= length)
998 public int read(byte[] buf, int off, int len) throws IOException
1000 int count = super.read(buf, off, (int) Math.min(len, (length != 0
1002 : Integer.MAX_VALUE)));
1003 if (count == -1 || (length > 0 && pos >= length))
1008 for (int i = 0; i < md.length; i++)
1009 md[i].update(buf, off, count);
1011 if (length != 0 && pos >= length)
1016 public int read(byte[] buf) throws IOException
1018 return read(buf, 0, buf.length);
1021 public long skip(long bytes) throws IOException
1023 byte[] b = new byte[1024];
1025 while (amount < bytes)
1027 int l = read(b, 0, (int) Math.min(b.length, bytes - amount));
1035 private void eof() throws IOException
1040 for (int i = 0; i < md.length; i++)
1042 byte[] hash = md[i].digest();
1044 debug("verifying " + md[i].getAlgorithm() + " expect="
1045 + new java.math.BigInteger(hashes[i]).toString(16)
1046 + " comp=" + new java.math.BigInteger(hash).toString(16));
1047 if (!Arrays.equals(hash, hashes[i]))
1049 synchronized(jarfile)
1052 debug(entry + " could NOT be verified");
1053 jarfile.verified.put(entry.getName(), Boolean.FALSE);
1056 // XXX ??? what do we do here?
1057 // throw new ZipException("message digest mismatch");
1061 synchronized(jarfile)
1064 debug(entry + " has been VERIFIED");
1065 jarfile.verified.put(entry.getName(), Boolean.TRUE);