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