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