1 /* JarFile.java - Representation of a jar file 2 Copyright (C) 2000, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package java.util.jar; 40 41 import gnu.java.io.Base64InputStream; 42 import gnu.java.security.OID; 43 import gnu.java.security.pkcs.PKCS7SignedData; 44 import gnu.java.security.pkcs.SignerInfo; 45 import gnu.java.security.provider.Gnu; 46 47 import java.io.ByteArrayOutputStream; 48 import java.io.File; 49 import java.io.FileNotFoundException; 50 import java.io.FilterInputStream; 51 import java.io.IOException; 52 import java.io.InputStream; 53 import java.security.InvalidKeyException; 54 import java.security.MessageDigest; 55 import java.security.NoSuchAlgorithmException; 56 import java.security.Signature; 57 import java.security.SignatureException; 58 import java.security.cert.CRLException; 59 import java.security.cert.Certificate; 60 import java.security.cert.CertificateException; 61 import java.security.cert.X509Certificate; 62 import java.util.Arrays; 63 import java.util.Enumeration; 64 import java.util.HashMap; 65 import java.util.HashSet; 66 import java.util.Iterator; 67 import java.util.LinkedList; 68 import java.util.List; 69 import java.util.Map; 70 import java.util.Set; 71 import java.util.regex.Matcher; 72 import java.util.regex.Pattern; 73 import java.util.zip.ZipEntry; 74 import java.util.zip.ZipException; 75 import java.util.zip.ZipFile; 76 77 /** 78 * Representation of a jar file. 79 * <p> 80 * Note that this class is not a subclass of java.io.File but a subclass of 81 * java.util.zip.ZipFile and you can only read JarFiles with it (although 82 * there are constructors that take a File object). 83 * 84 * @since 1.2 85 * @author Mark Wielaard (mark@klomp.org) 86 * @author Casey Marshall (csm@gnu.org) wrote the certificate and entry 87 * verification code. 88 */ 89 public class JarFile extends ZipFile 90 { 91 // Fields 92 93 /** The name of the manifest entry: META-INF/MANIFEST.MF */ 94 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 95 96 /** The META-INF directory entry. */ 97 private static final String META_INF = "META-INF/"; 98 99 /** 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; debug(Object msg)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 */ JarFile(String fileName)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 */ JarFile(String fileName, boolean verify)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 */ JarFile(File file)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 */ JarFile(File file, boolean verify)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 */ JarFile(File file, boolean verify, int mode)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 */ verify()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 */ readManifest()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 */ entries()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 JarEnumeration(Enumeration<? extends ZipEntry> e, JarFile f)339 JarEnumeration(Enumeration<? extends ZipEntry> e, JarFile f) 340 { 341 entries = e; 342 jarfile = f; 343 } 344 hasMoreElements()345 public boolean hasMoreElements() 346 { 347 return entries.hasMoreElements(); 348 } 349 nextElement()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 */ getEntry(String name)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 */ getInputStream(ZipEntry entry)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 */ getJarEntry(String name)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 */ getManifest()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. readSignatures()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 */ verify(Certificate[] certs, SignerInfo signerInfo, String alias, Set validCerts)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 */ verifyHashes(String entry, Attributes attr, HashMap hmManifestEntries)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 EntryInputStream(final ZipEntry entry, final InputStream in, final JarFile jar)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 markSupported()881 public boolean markSupported() 882 { 883 return false; 884 } 885 mark(int readLimit)886 public void mark(int readLimit) 887 { 888 } 889 reset()890 public void reset() 891 { 892 } 893 read()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 read(byte[] buf, int off, int len)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 read(byte[] buf)928 public int read(byte[] buf) throws IOException 929 { 930 return read(buf, 0, buf.length); 931 } 932 skip(long bytes)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 eof()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 } 982