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