1 /* 2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.jar; 27 28 import java.io.*; 29 import java.lang.ref.SoftReference; 30 import java.net.URL; 31 import java.util.*; 32 import java.util.stream.Stream; 33 import java.util.stream.StreamSupport; 34 import java.util.zip.*; 35 import java.security.CodeSigner; 36 import java.security.cert.Certificate; 37 import java.security.AccessController; 38 import java.security.CodeSource; 39 import jdk.internal.misc.SharedSecrets; 40 import sun.misc.IOUtils; 41 import sun.security.action.GetPropertyAction; 42 import sun.security.util.ManifestEntryVerifier; 43 import sun.security.util.SignatureFileVerifier; 44 45 /** 46 * The {@code JarFile} class is used to read the contents of a jar file 47 * from any file that can be opened with {@code java.io.RandomAccessFile}. 48 * It extends the class {@code java.util.zip.ZipFile} with support 49 * for reading an optional {@code Manifest} entry. The 50 * {@code Manifest} can be used to specify meta-information about the 51 * jar file and its entries. 52 * 53 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor 54 * or method in this class will cause a {@link NullPointerException} to be 55 * thrown. 56 * 57 * If the verify flag is on when opening a signed jar file, the content of the 58 * file is verified against its signature embedded inside the file. Please note 59 * that the verification process does not include validating the signer's 60 * certificate. A caller should inspect the return value of 61 * {@link JarEntry#getCodeSigners()} to further determine if the signature 62 * can be trusted. 63 * 64 * @author David Connelly 65 * @see Manifest 66 * @see java.util.zip.ZipFile 67 * @see java.util.jar.JarEntry 68 * @since 1.2 69 */ 70 public 71 class JarFile extends ZipFile { 72 private SoftReference<Manifest> manRef; 73 private JarEntry manEntry; 74 private JarVerifier jv; 75 private boolean jvInitialized; 76 private boolean verify; 77 78 // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true) 79 private boolean hasClassPathAttribute; 80 // true if manifest checked for special attributes 81 private volatile boolean hasCheckedSpecialAttributes; 82 83 // Set up JavaUtilJarAccess in SharedSecrets 84 static { 85 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl()); 86 } 87 88 /** 89 * The JAR manifest file name. 90 */ 91 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 92 93 /** 94 * Creates a new {@code JarFile} to read from the specified 95 * file {@code name}. The {@code JarFile} will be verified if 96 * it is signed. 97 * @param name the name of the jar file to be opened for reading 98 * @throws IOException if an I/O error has occurred 99 * @throws SecurityException if access to the file is denied 100 * by the SecurityManager 101 */ 102 public JarFile(String name) throws IOException { 103 this(new File(name), true, ZipFile.OPEN_READ); 104 } 105 106 /** 107 * Creates a new {@code JarFile} to read from the specified 108 * file {@code name}. 109 * @param name the name of the jar file to be opened for reading 110 * @param verify whether or not to verify the jar file if 111 * it is signed. 112 * @throws IOException if an I/O error has occurred 113 * @throws SecurityException if access to the file is denied 114 * by the SecurityManager 115 */ 116 public JarFile(String name, boolean verify) throws IOException { 117 this(new File(name), verify, ZipFile.OPEN_READ); 118 } 119 120 /** 121 * Creates a new {@code JarFile} to read from the specified 122 * {@code File} object. The {@code JarFile} will be verified if 123 * it is signed. 124 * @param file the jar file to be opened for reading 125 * @throws IOException if an I/O error has occurred 126 * @throws SecurityException if access to the file is denied 127 * by the SecurityManager 128 */ 129 public JarFile(File file) throws IOException { 130 this(file, true, ZipFile.OPEN_READ); 131 } 132 133 134 /** 135 * Creates a new {@code JarFile} to read from the specified 136 * {@code File} object. 137 * @param file the jar file to be opened for reading 138 * @param verify whether or not to verify the jar file if 139 * it is signed. 140 * @throws IOException if an I/O error has occurred 141 * @throws SecurityException if access to the file is denied 142 * by the SecurityManager. 143 */ 144 public JarFile(File file, boolean verify) throws IOException { 145 this(file, verify, ZipFile.OPEN_READ); 146 } 147 148 149 /** 150 * Creates a new {@code JarFile} to read from the specified 151 * {@code File} object in the specified mode. The mode argument 152 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 153 * 154 * @param file the jar file to be opened for reading 155 * @param verify whether or not to verify the jar file if 156 * it is signed. 157 * @param mode the mode in which the file is to be opened 158 * @throws IOException if an I/O error has occurred 159 * @throws IllegalArgumentException 160 * if the {@code mode} argument is invalid 161 * @throws SecurityException if access to the file is denied 162 * by the SecurityManager 163 * @since 1.3 164 */ 165 public JarFile(File file, boolean verify, int mode) throws IOException { 166 super(file, mode); 167 this.verify = verify; 168 } 169 170 /** 171 * Returns the jar file manifest, or {@code null} if none. 172 * 173 * @return the jar file manifest, or {@code null} if none 174 * 175 * @throws IllegalStateException 176 * may be thrown if the jar file has been closed 177 * @throws IOException if an I/O error has occurred 178 */ 179 public Manifest getManifest() throws IOException { 180 return getManifestFromReference(); 181 } 182 183 private Manifest getManifestFromReference() throws IOException { 184 Manifest man = manRef != null ? manRef.get() : null; 185 186 if (man == null) { 187 188 JarEntry manEntry = getManEntry(); 189 190 // If found then load the manifest 191 if (manEntry != null) { 192 if (verify) { 193 byte[] b = getBytes(manEntry); 194 man = new Manifest(new ByteArrayInputStream(b)); 195 if (!jvInitialized) { 196 jv = new JarVerifier(b); 197 } 198 } else { 199 man = new Manifest(super.getInputStream(manEntry)); 200 } 201 manRef = new SoftReference<>(man); 202 } 203 } 204 return man; 205 } 206 207 private native String[] getMetaInfEntryNames(); 208 209 /** 210 * Returns the {@code JarEntry} for the given entry name or 211 * {@code null} if not found. 212 * 213 * @param name the jar file entry name 214 * @return the {@code JarEntry} for the given entry name or 215 * {@code null} if not found. 216 * 217 * @throws IllegalStateException 218 * may be thrown if the jar file has been closed 219 * 220 * @see java.util.jar.JarEntry 221 */ 222 public JarEntry getJarEntry(String name) { 223 return (JarEntry)getEntry(name); 224 } 225 226 /** 227 * Returns the {@code ZipEntry} for the given entry name or 228 * {@code null} if not found. 229 * 230 * @param name the jar file entry name 231 * @return the {@code ZipEntry} for the given entry name or 232 * {@code null} if not found 233 * 234 * @throws IllegalStateException 235 * may be thrown if the jar file has been closed 236 * 237 * @see java.util.zip.ZipEntry 238 */ 239 public ZipEntry getEntry(String name) { 240 ZipEntry ze = super.getEntry(name); 241 if (ze != null) { 242 return new JarFileEntry(ze); 243 } 244 return null; 245 } 246 247 private class JarEntryIterator implements Enumeration<JarEntry>, 248 Iterator<JarEntry> 249 { 250 final Enumeration<? extends ZipEntry> e = JarFile.super.entries(); 251 252 public boolean hasNext() { 253 return e.hasMoreElements(); 254 } 255 256 public JarEntry next() { 257 ZipEntry ze = e.nextElement(); 258 return new JarFileEntry(ze); 259 } 260 261 public boolean hasMoreElements() { 262 return hasNext(); 263 } 264 265 public JarEntry nextElement() { 266 return next(); 267 } 268 269 public Iterator<JarEntry> asIterator() { 270 return this; 271 } 272 } 273 274 /** 275 * Returns an enumeration of the jar file entries. 276 * 277 * @return an enumeration of the jar file entries 278 * @throws IllegalStateException 279 * may be thrown if the jar file has been closed 280 */ 281 public Enumeration<JarEntry> entries() { 282 return new JarEntryIterator(); 283 } 284 285 /** 286 * Returns an ordered {@code Stream} over the jar file entries. 287 * Entries appear in the {@code Stream} in the order they appear in 288 * the central directory of the jar file. 289 * 290 * @return an ordered {@code Stream} of entries in this jar file 291 * @throws IllegalStateException if the jar file has been closed 292 * @since 1.8 293 */ 294 public Stream<JarEntry> stream() { 295 return StreamSupport.stream(Spliterators.spliterator( 296 new JarEntryIterator(), size(), 297 Spliterator.ORDERED | Spliterator.DISTINCT | 298 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); 299 } 300 301 private class JarFileEntry extends JarEntry { 302 JarFileEntry(ZipEntry ze) { 303 super(ze); 304 } 305 public Attributes getAttributes() throws IOException { 306 Manifest man = JarFile.this.getManifest(); 307 if (man != null) { 308 return man.getAttributes(getName()); 309 } else { 310 return null; 311 } 312 } 313 public Certificate[] getCertificates() { 314 try { 315 maybeInstantiateVerifier(); 316 } catch (IOException e) { 317 throw new RuntimeException(e); 318 } 319 if (certs == null && jv != null) { 320 certs = jv.getCerts(JarFile.this, this); 321 } 322 return certs == null ? null : certs.clone(); 323 } 324 public CodeSigner[] getCodeSigners() { 325 try { 326 maybeInstantiateVerifier(); 327 } catch (IOException e) { 328 throw new RuntimeException(e); 329 } 330 if (signers == null && jv != null) { 331 signers = jv.getCodeSigners(JarFile.this, this); 332 } 333 return signers == null ? null : signers.clone(); 334 } 335 } 336 337 /* 338 * Ensures that the JarVerifier has been created if one is 339 * necessary (i.e., the jar appears to be signed.) This is done as 340 * a quick check to avoid processing of the manifest for unsigned 341 * jars. 342 */ 343 private void maybeInstantiateVerifier() throws IOException { 344 if (jv != null) { 345 return; 346 } 347 348 if (verify) { 349 String[] names = getMetaInfEntryNames(); 350 if (names != null) { 351 for (String nameLower : names) { 352 String name = nameLower.toUpperCase(Locale.ENGLISH); 353 if (name.endsWith(".DSA") || 354 name.endsWith(".RSA") || 355 name.endsWith(".EC") || 356 name.endsWith(".SF")) { 357 // Assume since we found a signature-related file 358 // that the jar is signed and that we therefore 359 // need a JarVerifier and Manifest 360 getManifest(); 361 return; 362 } 363 } 364 } 365 // No signature-related files; don't instantiate a 366 // verifier 367 verify = false; 368 } 369 } 370 371 372 /* 373 * Initializes the verifier object by reading all the manifest 374 * entries and passing them to the verifier. 375 */ 376 private void initializeVerifier() { 377 ManifestEntryVerifier mev = null; 378 379 // Verify "META-INF/" entries... 380 try { 381 String[] names = getMetaInfEntryNames(); 382 if (names != null) { 383 for (String name : names) { 384 String uname = name.toUpperCase(Locale.ENGLISH); 385 if (MANIFEST_NAME.equals(uname) 386 || SignatureFileVerifier.isBlockOrSF(uname)) { 387 JarEntry e = getJarEntry(name); 388 if (e == null) { 389 throw new JarException("corrupted jar file"); 390 } 391 if (mev == null) { 392 mev = new ManifestEntryVerifier 393 (getManifestFromReference()); 394 } 395 byte[] b = getBytes(e); 396 if (b != null && b.length > 0) { 397 jv.beginEntry(e, mev); 398 jv.update(b.length, b, 0, b.length, mev); 399 jv.update(-1, null, 0, 0, mev); 400 } 401 } 402 } 403 } 404 } catch (IOException ex) { 405 // if we had an error parsing any blocks, just 406 // treat the jar file as being unsigned 407 jv = null; 408 verify = false; 409 if (JarVerifier.debug != null) { 410 JarVerifier.debug.println("jarfile parsing error!"); 411 ex.printStackTrace(); 412 } 413 } 414 415 // if after initializing the verifier we have nothing 416 // signed, we null it out. 417 418 if (jv != null) { 419 420 jv.doneWithMeta(); 421 if (JarVerifier.debug != null) { 422 JarVerifier.debug.println("done with meta!"); 423 } 424 425 if (jv.nothingToVerify()) { 426 if (JarVerifier.debug != null) { 427 JarVerifier.debug.println("nothing to verify!"); 428 } 429 jv = null; 430 verify = false; 431 } 432 } 433 } 434 435 /* 436 * Reads all the bytes for a given entry. Used to process the 437 * META-INF files. 438 */ 439 private byte[] getBytes(ZipEntry ze) throws IOException { 440 try (InputStream is = super.getInputStream(ze)) { 441 return IOUtils.readFully(is, (int)ze.getSize(), true); 442 } 443 } 444 445 /** 446 * Returns an input stream for reading the contents of the specified 447 * zip file entry. 448 * @param ze the zip file entry 449 * @return an input stream for reading the contents of the specified 450 * zip file entry 451 * @throws ZipException if a zip file format error has occurred 452 * @throws IOException if an I/O error has occurred 453 * @throws SecurityException if any of the jar file entries 454 * are incorrectly signed. 455 * @throws IllegalStateException 456 * may be thrown if the jar file has been closed 457 */ 458 public synchronized InputStream getInputStream(ZipEntry ze) 459 throws IOException 460 { 461 maybeInstantiateVerifier(); 462 if (jv == null) { 463 return super.getInputStream(ze); 464 } 465 if (!jvInitialized) { 466 initializeVerifier(); 467 jvInitialized = true; 468 // could be set to null after a call to 469 // initializeVerifier if we have nothing to 470 // verify 471 if (jv == null) 472 return super.getInputStream(ze); 473 } 474 475 // wrap a verifier stream around the real stream 476 return new JarVerifier.VerifierStream( 477 getManifestFromReference(), 478 ze instanceof JarFileEntry ? 479 (JarEntry) ze : getJarEntry(ze.getName()), 480 super.getInputStream(ze), 481 jv); 482 } 483 484 // Statics for hand-coded Boyer-Moore search 485 private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'}; 486 // The bad character shift for "class-path" 487 private static final int[] CLASSPATH_LASTOCC; 488 // The good suffix shift for "class-path" 489 private static final int[] CLASSPATH_OPTOSFT; 490 491 static { 492 CLASSPATH_LASTOCC = new int[128]; 493 CLASSPATH_OPTOSFT = new int[10]; 494 CLASSPATH_LASTOCC[(int)'c'] = 1; 495 CLASSPATH_LASTOCC[(int)'l'] = 2; 496 CLASSPATH_LASTOCC[(int)'s'] = 5; 497 CLASSPATH_LASTOCC[(int)'-'] = 6; 498 CLASSPATH_LASTOCC[(int)'p'] = 7; 499 CLASSPATH_LASTOCC[(int)'a'] = 8; 500 CLASSPATH_LASTOCC[(int)'t'] = 9; 501 CLASSPATH_LASTOCC[(int)'h'] = 10; 502 for (int i=0; i<9; i++) 503 CLASSPATH_OPTOSFT[i] = 10; 504 CLASSPATH_OPTOSFT[9]=1; 505 } 506 507 private JarEntry getManEntry() { 508 if (manEntry == null) { 509 // First look up manifest entry using standard name 510 manEntry = getJarEntry(MANIFEST_NAME); 511 if (manEntry == null) { 512 // If not found, then iterate through all the "META-INF/" 513 // entries to find a match. 514 String[] names = getMetaInfEntryNames(); 515 if (names != null) { 516 for (String name : names) { 517 if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) { 518 manEntry = getJarEntry(name); 519 break; 520 } 521 } 522 } 523 } 524 } 525 return manEntry; 526 } 527 528 /** 529 * Returns {@code true} iff this JAR file has a manifest with the 530 * Class-Path attribute 531 */ 532 boolean hasClassPathAttribute() throws IOException { 533 checkForSpecialAttributes(); 534 return hasClassPathAttribute; 535 } 536 537 /** 538 * Returns true if the pattern {@code src} is found in {@code b}. 539 * The {@code lastOcc} and {@code optoSft} arrays are the precomputed 540 * bad character and good suffix shifts. 541 */ 542 private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) { 543 int len = src.length; 544 int last = b.length - len; 545 int i = 0; 546 next: 547 while (i<=last) { 548 for (int j=(len-1); j>=0; j--) { 549 char c = (char) b[i+j]; 550 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; 551 if (c != src[j]) { 552 i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); 553 continue next; 554 } 555 } 556 return true; 557 } 558 return false; 559 } 560 561 /** 562 * On first invocation, check if the JAR file has the Class-Path 563 * attribute. A no-op on subsequent calls. 564 */ 565 private void checkForSpecialAttributes() throws IOException { 566 if (hasCheckedSpecialAttributes) return; 567 JarEntry manEntry = getManEntry(); 568 if (manEntry != null) { 569 byte[] b = getBytes(manEntry); 570 if (match(CLASSPATH_CHARS, b, CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT)) 571 hasClassPathAttribute = true; 572 } 573 hasCheckedSpecialAttributes = true; 574 } 575 576 private synchronized void ensureInitialization() { 577 try { 578 maybeInstantiateVerifier(); 579 } catch (IOException e) { 580 throw new RuntimeException(e); 581 } 582 if (jv != null && !jvInitialized) { 583 initializeVerifier(); 584 jvInitialized = true; 585 } 586 } 587 588 JarEntry newEntry(ZipEntry ze) { 589 return new JarFileEntry(ze); 590 } 591 592 Enumeration<String> entryNames(CodeSource[] cs) { 593 ensureInitialization(); 594 if (jv != null) { 595 return jv.entryNames(this, cs); 596 } 597 598 /* 599 * JAR file has no signed content. Is there a non-signing 600 * code source? 601 */ 602 boolean includeUnsigned = false; 603 for (CodeSource c : cs) { 604 if (c.getCodeSigners() == null) { 605 includeUnsigned = true; 606 break; 607 } 608 } 609 if (includeUnsigned) { 610 return unsignedEntryNames(); 611 } else { 612 return new Enumeration<>() { 613 614 public boolean hasMoreElements() { 615 return false; 616 } 617 618 public String nextElement() { 619 throw new NoSuchElementException(); 620 } 621 }; 622 } 623 } 624 625 /** 626 * Returns an enumeration of the zip file entries 627 * excluding internal JAR mechanism entries and including 628 * signed entries missing from the ZIP directory. 629 */ 630 Enumeration<JarEntry> entries2() { 631 ensureInitialization(); 632 if (jv != null) { 633 return jv.entries2(this, super.entries()); 634 } 635 636 // screen out entries which are never signed 637 final Enumeration<? extends ZipEntry> enum_ = super.entries(); 638 return new Enumeration<>() { 639 640 ZipEntry entry; 641 642 public boolean hasMoreElements() { 643 if (entry != null) { 644 return true; 645 } 646 while (enum_.hasMoreElements()) { 647 ZipEntry ze = enum_.nextElement(); 648 if (JarVerifier.isSigningRelated(ze.getName())) { 649 continue; 650 } 651 entry = ze; 652 return true; 653 } 654 return false; 655 } 656 657 public JarFileEntry nextElement() { 658 if (hasMoreElements()) { 659 ZipEntry ze = entry; 660 entry = null; 661 return new JarFileEntry(ze); 662 } 663 throw new NoSuchElementException(); 664 } 665 }; 666 } 667 668 CodeSource[] getCodeSources(URL url) { 669 ensureInitialization(); 670 if (jv != null) { 671 return jv.getCodeSources(this, url); 672 } 673 674 /* 675 * JAR file has no signed content. Is there a non-signing 676 * code source? 677 */ 678 Enumeration<String> unsigned = unsignedEntryNames(); 679 if (unsigned.hasMoreElements()) { 680 return new CodeSource[]{JarVerifier.getUnsignedCS(url)}; 681 } else { 682 return null; 683 } 684 } 685 686 private Enumeration<String> unsignedEntryNames() { 687 final Enumeration<JarEntry> entries = entries(); 688 return new Enumeration<>() { 689 690 String name; 691 692 /* 693 * Grab entries from ZIP directory but screen out 694 * metadata. 695 */ 696 public boolean hasMoreElements() { 697 if (name != null) { 698 return true; 699 } 700 while (entries.hasMoreElements()) { 701 String value; 702 ZipEntry e = entries.nextElement(); 703 value = e.getName(); 704 if (e.isDirectory() || JarVerifier.isSigningRelated(value)) { 705 continue; 706 } 707 name = value; 708 return true; 709 } 710 return false; 711 } 712 713 public String nextElement() { 714 if (hasMoreElements()) { 715 String value = name; 716 name = null; 717 return value; 718 } 719 throw new NoSuchElementException(); 720 } 721 }; 722 } 723 724 CodeSource getCodeSource(URL url, String name) { 725 ensureInitialization(); 726 if (jv != null) { 727 if (jv.eagerValidation) { 728 CodeSource cs = null; 729 JarEntry je = getJarEntry(name); 730 if (je != null) { 731 cs = jv.getCodeSource(url, this, je); 732 } else { 733 cs = jv.getCodeSource(url, name); 734 } 735 return cs; 736 } else { 737 return jv.getCodeSource(url, name); 738 } 739 } 740 741 return JarVerifier.getUnsignedCS(url); 742 } 743 744 void setEagerValidation(boolean eager) { 745 try { 746 maybeInstantiateVerifier(); 747 } catch (IOException e) { 748 throw new RuntimeException(e); 749 } 750 if (jv != null) { 751 jv.setEagerValidation(eager); 752 } 753 } 754 755 List<Object> getManifestDigests() { 756 ensureInitialization(); 757 if (jv != null) { 758 return jv.getManifestDigests(); 759 } 760 return new ArrayList<>(); 761 } 762 }