1 /* 2 * Copyright (c) 1997, 2018, 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 jdk.internal.access.SharedSecrets; 29 import jdk.internal.access.JavaUtilZipFileAccess; 30 import sun.security.action.GetPropertyAction; 31 import sun.security.util.ManifestEntryVerifier; 32 import sun.security.util.SignatureFileVerifier; 33 34 import java.io.ByteArrayInputStream; 35 import java.io.EOFException; 36 import java.io.File; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.lang.ref.SoftReference; 40 import java.net.URL; 41 import java.security.CodeSigner; 42 import java.security.CodeSource; 43 import java.security.cert.Certificate; 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.Enumeration; 47 import java.util.List; 48 import java.util.Locale; 49 import java.util.NoSuchElementException; 50 import java.util.Objects; 51 import java.util.function.Function; 52 import java.util.stream.Stream; 53 import java.util.zip.ZipEntry; 54 import java.util.zip.ZipException; 55 import java.util.zip.ZipFile; 56 57 /** 58 * The {@code JarFile} class is used to read the contents of a jar file 59 * from any file that can be opened with {@code java.io.RandomAccessFile}. 60 * It extends the class {@code java.util.zip.ZipFile} with support 61 * for reading an optional {@code Manifest} entry, and support for 62 * processing multi-release jar files. The {@code Manifest} can be used 63 * to specify meta-information about the jar file and its entries. 64 * 65 * <p><a id="multirelease">A multi-release jar file</a> is a jar file that 66 * contains a manifest with a main attribute named "Multi-Release", 67 * a set of "base" entries, some of which are public classes with public 68 * or protected methods that comprise the public interface of the jar file, 69 * and a set of "versioned" entries contained in subdirectories of the 70 * "META-INF/versions" directory. The versioned entries are partitioned by the 71 * major version of the Java platform. A versioned entry, with a version 72 * {@code n}, {@code 8 < n}, in the "META-INF/versions/{n}" directory overrides 73 * the base entry as well as any entry with a version number {@code i} where 74 * {@code 8 < i < n}. 75 * 76 * <p>By default, a {@code JarFile} for a multi-release jar file is configured 77 * to process the multi-release jar file as if it were a plain (unversioned) jar 78 * file, and as such an entry name is associated with at most one base entry. 79 * The {@code JarFile} may be configured to process a multi-release jar file by 80 * creating the {@code JarFile} with the 81 * {@link JarFile#JarFile(File, boolean, int, Runtime.Version)} constructor. The 82 * {@code Runtime.Version} object sets a maximum version used when searching for 83 * versioned entries. When so configured, an entry name 84 * can correspond with at most one base entry and zero or more versioned 85 * entries. A search is required to associate the entry name with the latest 86 * versioned entry whose version is less than or equal to the maximum version 87 * (see {@link #getEntry(String)}). 88 * 89 * <p>Class loaders that utilize {@code JarFile} to load classes from the 90 * contents of {@code JarFile} entries should construct the {@code JarFile} 91 * by invoking the {@link JarFile#JarFile(File, boolean, int, Runtime.Version)} 92 * constructor with the value {@code Runtime.version()} assigned to the last 93 * argument. This assures that classes compatible with the major 94 * version of the running JVM are loaded from multi-release jar files. 95 * 96 * <p> If the {@code verify} flag is on when opening a signed jar file, the content 97 * of the jar entry is verified against the signature embedded inside the manifest 98 * that is associated with its {@link JarEntry#getRealName() path name}. For a 99 * multi-release jar file, the content of a versioned entry is verfieid against 100 * its own signature and {@link JarEntry#getCodeSigners()} returns its own signers. 101 * 102 * Please note that the verification process does not include validating the 103 * signer's certificate. A caller should inspect the return value of 104 * {@link JarEntry#getCodeSigners()} to further determine if the signature 105 * can be trusted. 106 * 107 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor 108 * or method in this class will cause a {@link NullPointerException} to be 109 * thrown. 110 * 111 * @implNote 112 * <div class="block"> 113 * If the API can not be used to configure a {@code JarFile} (e.g. to override 114 * the configuration of a compiled application or library), two {@code System} 115 * properties are available. 116 * <ul> 117 * <li> 118 * {@code jdk.util.jar.version} can be assigned a value that is the 119 * {@code String} representation of a non-negative integer 120 * {@code <= Runtime.version().feature()}. The value is used to set the effective 121 * runtime version to something other than the default value obtained by 122 * evaluating {@code Runtime.version().feature()}. The effective runtime version 123 * is the version that the {@link JarFile#JarFile(File, boolean, int, Runtime.Version)} 124 * constructor uses when the value of the last argument is 125 * {@code JarFile.runtimeVersion()}. 126 * </li> 127 * <li> 128 * {@code jdk.util.jar.enableMultiRelease} can be assigned one of the three 129 * {@code String} values <em>true</em>, <em>false</em>, or <em>force</em>. The 130 * value <em>true</em>, the default value, enables multi-release jar file 131 * processing. The value <em>false</em> disables multi-release jar processing, 132 * ignoring the "Multi-Release" manifest attribute, and the versioned 133 * directories in a multi-release jar file if they exist. Furthermore, 134 * the method {@link JarFile#isMultiRelease()} returns <em>false</em>. The value 135 * <em>force</em> causes the {@code JarFile} to be initialized to runtime 136 * versioning after construction. It effectively does the same as this code: 137 * {@code (new JarFile(File, boolean, int, JarFile.runtimeVersion())}. 138 * </li> 139 * </ul> 140 * </div> 141 * 142 * @author David Connelly 143 * @see Manifest 144 * @see java.util.zip.ZipFile 145 * @see java.util.jar.JarEntry 146 * @since 1.2 147 */ 148 public 149 class JarFile extends ZipFile { 150 private final static Runtime.Version BASE_VERSION; 151 private final static int BASE_VERSION_FEATURE; 152 private final static Runtime.Version RUNTIME_VERSION; 153 private final static boolean MULTI_RELEASE_ENABLED; 154 private final static boolean MULTI_RELEASE_FORCED; 155 private SoftReference<Manifest> manRef; 156 private JarEntry manEntry; 157 private JarVerifier jv; 158 private boolean jvInitialized; 159 private boolean verify; 160 private final Runtime.Version version; // current version 161 private final int versionFeature; // version.feature() 162 private boolean isMultiRelease; // is jar multi-release? 163 164 // indicates if Class-Path attribute present 165 private boolean hasClassPathAttribute; 166 // true if manifest checked for special attributes 167 private volatile boolean hasCheckedSpecialAttributes; 168 169 private static final JavaUtilZipFileAccess JUZFA; 170 171 static { 172 // Set up JavaUtilJarAccess in SharedSecrets 173 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl()); 174 // Get JavaUtilZipFileAccess from SharedSecrets 175 JUZFA = SharedSecrets.getJavaUtilZipFileAccess(); 176 // multi-release jar file versions >= 9 177 BASE_VERSION = Runtime.Version.parse(Integer.toString(8)); 178 BASE_VERSION_FEATURE = BASE_VERSION.feature(); 179 String jarVersion = GetPropertyAction.privilegedGetProperty("jdk.util.jar.version"); 180 int runtimeVersion = Runtime.version().feature(); 181 if (jarVersion != null) { 182 int jarVer = Integer.parseInt(jarVersion); 183 runtimeVersion = (jarVer > runtimeVersion) 184 ? runtimeVersion 185 : Math.max(jarVer, BASE_VERSION_FEATURE); 186 } 187 RUNTIME_VERSION = Runtime.Version.parse(Integer.toString(runtimeVersion)); 188 String enableMultiRelease = GetPropertyAction 189 .privilegedGetProperty("jdk.util.jar.enableMultiRelease", "true"); 190 switch (enableMultiRelease) { 191 case "true": 192 default: 193 MULTI_RELEASE_ENABLED = true; 194 MULTI_RELEASE_FORCED = false; 195 break; 196 case "false": 197 MULTI_RELEASE_ENABLED = false; 198 MULTI_RELEASE_FORCED = false; 199 break; 200 case "force": 201 MULTI_RELEASE_ENABLED = true; 202 MULTI_RELEASE_FORCED = true; 203 break; 204 } 205 } 206 207 private static final String META_INF = "META-INF/"; 208 209 private static final String META_INF_VERSIONS = META_INF + "versions/"; 210 211 /** 212 * The JAR manifest file name. 213 */ 214 public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF"; 215 216 /** 217 * Returns the version that represents the unversioned configuration of a 218 * multi-release jar file. 219 * 220 * @return the version that represents the unversioned configuration 221 * 222 * @since 9 223 */ 224 public static Runtime.Version baseVersion() { 225 return BASE_VERSION; 226 } 227 228 /** 229 * Returns the version that represents the effective runtime versioned 230 * configuration of a multi-release jar file. 231 * <p> 232 * By default the feature version number of the returned {@code Version} will 233 * be equal to the feature version number of {@code Runtime.version()}. 234 * However, if the {@code jdk.util.jar.version} property is set, the 235 * returned {@code Version} is derived from that property and feature version 236 * numbers may not be equal. 237 * 238 * @return the version that represents the runtime versioned configuration 239 * 240 * @since 9 241 */ 242 public static Runtime.Version runtimeVersion() { 243 return RUNTIME_VERSION; 244 } 245 246 /** 247 * Creates a new {@code JarFile} to read from the specified 248 * file {@code name}. The {@code JarFile} will be verified if 249 * it is signed. 250 * @param name the name of the jar file to be opened for reading 251 * @throws IOException if an I/O error has occurred 252 * @throws SecurityException if access to the file is denied 253 * by the SecurityManager 254 */ 255 public JarFile(String name) throws IOException { 256 this(new File(name), true, ZipFile.OPEN_READ); 257 } 258 259 /** 260 * Creates a new {@code JarFile} to read from the specified 261 * file {@code name}. 262 * @param name the name of the jar file to be opened for reading 263 * @param verify whether or not to verify the jar file if 264 * it is signed. 265 * @throws IOException if an I/O error has occurred 266 * @throws SecurityException if access to the file is denied 267 * by the SecurityManager 268 */ 269 public JarFile(String name, boolean verify) throws IOException { 270 this(new File(name), verify, ZipFile.OPEN_READ); 271 } 272 273 /** 274 * Creates a new {@code JarFile} to read from the specified 275 * {@code File} object. The {@code JarFile} will be verified if 276 * it is signed. 277 * @param file the jar file to be opened for reading 278 * @throws IOException if an I/O error has occurred 279 * @throws SecurityException if access to the file is denied 280 * by the SecurityManager 281 */ 282 public JarFile(File file) throws IOException { 283 this(file, true, ZipFile.OPEN_READ); 284 } 285 286 /** 287 * Creates a new {@code JarFile} to read from the specified 288 * {@code File} object. 289 * @param file the jar file to be opened for reading 290 * @param verify whether or not to verify the jar file if 291 * it is signed. 292 * @throws IOException if an I/O error has occurred 293 * @throws SecurityException if access to the file is denied 294 * by the SecurityManager. 295 */ 296 public JarFile(File file, boolean verify) throws IOException { 297 this(file, verify, ZipFile.OPEN_READ); 298 } 299 300 /** 301 * Creates a new {@code JarFile} to read from the specified 302 * {@code File} object in the specified mode. The mode argument 303 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 304 * 305 * @param file the jar file to be opened for reading 306 * @param verify whether or not to verify the jar file if 307 * it is signed. 308 * @param mode the mode in which the file is to be opened 309 * @throws IOException if an I/O error has occurred 310 * @throws IllegalArgumentException 311 * if the {@code mode} argument is invalid 312 * @throws SecurityException if access to the file is denied 313 * by the SecurityManager 314 * @since 1.3 315 */ 316 public JarFile(File file, boolean verify, int mode) throws IOException { 317 this(file, verify, mode, BASE_VERSION); 318 } 319 320 /** 321 * Creates a new {@code JarFile} to read from the specified 322 * {@code File} object in the specified mode. The mode argument 323 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 324 * The version argument, after being converted to a canonical form, is 325 * used to configure the {@code JarFile} for processing 326 * multi-release jar files. 327 * <p> 328 * The canonical form derived from the version parameter is 329 * {@code Runtime.Version.parse(Integer.toString(n))} where {@code n} is 330 * {@code Math.max(version.feature(), JarFile.baseVersion().feature())}. 331 * 332 * @param file the jar file to be opened for reading 333 * @param verify whether or not to verify the jar file if 334 * it is signed. 335 * @param mode the mode in which the file is to be opened 336 * @param version specifies the release version for a multi-release jar file 337 * @throws IOException if an I/O error has occurred 338 * @throws IllegalArgumentException 339 * if the {@code mode} argument is invalid 340 * @throws SecurityException if access to the file is denied 341 * by the SecurityManager 342 * @throws NullPointerException if {@code version} is {@code null} 343 * @since 9 344 */ 345 public JarFile(File file, boolean verify, int mode, Runtime.Version version) throws IOException { 346 super(file, mode); 347 this.verify = verify; 348 Objects.requireNonNull(version); 349 if (MULTI_RELEASE_FORCED || version.feature() == RUNTIME_VERSION.feature()) { 350 // This deals with the common case where the value from JarFile.runtimeVersion() is passed 351 this.version = RUNTIME_VERSION; 352 } else if (version.feature() <= BASE_VERSION_FEATURE) { 353 // This also deals with the common case where the value from JarFile.baseVersion() is passed 354 this.version = BASE_VERSION; 355 } else { 356 // Canonicalize 357 this.version = Runtime.Version.parse(Integer.toString(version.feature())); 358 } 359 this.versionFeature = this.version.feature(); 360 } 361 362 /** 363 * Returns the maximum version used when searching for versioned entries. 364 * <p> 365 * If this {@code JarFile} is not a multi-release jar file or is not 366 * configured to be processed as such, then the version returned will be the 367 * same as that returned from {@link #baseVersion()}. 368 * 369 * @return the maximum version 370 * @since 9 371 */ 372 public final Runtime.Version getVersion() { 373 return isMultiRelease() ? this.version : BASE_VERSION; 374 } 375 376 /** 377 * Indicates whether or not this jar file is a multi-release jar file. 378 * 379 * @return true if this JarFile is a multi-release jar file 380 * @since 9 381 */ 382 public final boolean isMultiRelease() { 383 if (isMultiRelease) { 384 return true; 385 } 386 if (MULTI_RELEASE_ENABLED) { 387 try { 388 checkForSpecialAttributes(); 389 } catch (IOException io) { 390 isMultiRelease = false; 391 } 392 } 393 return isMultiRelease; 394 } 395 396 /** 397 * Returns the jar file manifest, or {@code null} if none. 398 * 399 * @return the jar file manifest, or {@code null} if none 400 * 401 * @throws IllegalStateException 402 * may be thrown if the jar file has been closed 403 * @throws IOException if an I/O error has occurred 404 */ 405 public Manifest getManifest() throws IOException { 406 return getManifestFromReference(); 407 } 408 409 private Manifest getManifestFromReference() throws IOException { 410 Manifest man = manRef != null ? manRef.get() : null; 411 412 if (man == null) { 413 414 JarEntry manEntry = getManEntry(); 415 416 // If found then load the manifest 417 if (manEntry != null) { 418 if (verify) { 419 byte[] b = getBytes(manEntry); 420 if (!jvInitialized) { 421 jv = new JarVerifier(b); 422 } 423 man = new Manifest(jv, new ByteArrayInputStream(b), getName()); 424 } else { 425 man = new Manifest(super.getInputStream(manEntry), getName()); 426 } 427 manRef = new SoftReference<>(man); 428 } 429 } 430 return man; 431 } 432 433 private String[] getMetaInfEntryNames() { 434 return JUZFA.getMetaInfEntryNames((ZipFile)this); 435 } 436 437 /** 438 * Returns the {@code JarEntry} for the given base entry name or 439 * {@code null} if not found. 440 * 441 * <p>If this {@code JarFile} is a multi-release jar file and is configured 442 * to be processed as such, then a search is performed to find and return 443 * a {@code JarEntry} that is the latest versioned entry associated with the 444 * given entry name. The returned {@code JarEntry} is the versioned entry 445 * corresponding to the given base entry name prefixed with the string 446 * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for 447 * which an entry exists. If such a versioned entry does not exist, then 448 * the {@code JarEntry} for the base entry is returned, otherwise 449 * {@code null} is returned if no entries are found. The initial value for 450 * the version {@code n} is the maximum version as returned by the method 451 * {@link JarFile#getVersion()}. 452 * 453 * @param name the jar file entry name 454 * @return the {@code JarEntry} for the given entry name, or 455 * the versioned entry name, or {@code null} if not found 456 * 457 * @throws IllegalStateException 458 * may be thrown if the jar file has been closed 459 * 460 * @see java.util.jar.JarEntry 461 * 462 * @implSpec 463 * <div class="block"> 464 * This implementation invokes {@link JarFile#getEntry(String)}. 465 * </div> 466 */ 467 public JarEntry getJarEntry(String name) { 468 return (JarEntry)getEntry(name); 469 } 470 471 /** 472 * Returns the {@code ZipEntry} for the given base entry name or 473 * {@code null} if not found. 474 * 475 * <p>If this {@code JarFile} is a multi-release jar file and is configured 476 * to be processed as such, then a search is performed to find and return 477 * a {@code ZipEntry} that is the latest versioned entry associated with the 478 * given entry name. The returned {@code ZipEntry} is the versioned entry 479 * corresponding to the given base entry name prefixed with the string 480 * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for 481 * which an entry exists. If such a versioned entry does not exist, then 482 * the {@code ZipEntry} for the base entry is returned, otherwise 483 * {@code null} is returned if no entries are found. The initial value for 484 * the version {@code n} is the maximum version as returned by the method 485 * {@link JarFile#getVersion()}. 486 * 487 * @param name the jar file entry name 488 * @return the {@code ZipEntry} for the given entry name or 489 * the versioned entry name or {@code null} if not found 490 * 491 * @throws IllegalStateException 492 * may be thrown if the jar file has been closed 493 * 494 * @see java.util.zip.ZipEntry 495 * 496 * @implSpec 497 * <div class="block"> 498 * This implementation may return a versioned entry for the requested name 499 * even if there is not a corresponding base entry. This can occur 500 * if there is a private or package-private versioned entry that matches. 501 * If a subclass overrides this method, assure that the override method 502 * invokes {@code super.getEntry(name)} to obtain all versioned entries. 503 * </div> 504 */ 505 public ZipEntry getEntry(String name) { 506 JarFileEntry je = getEntry0(name); 507 if (isMultiRelease()) { 508 return getVersionedEntry(name, je); 509 } 510 return je; 511 } 512 513 /** 514 * Returns an enumeration of the jar file entries. 515 * 516 * @return an enumeration of the jar file entries 517 * @throws IllegalStateException 518 * may be thrown if the jar file has been closed 519 */ 520 public Enumeration<JarEntry> entries() { 521 return JUZFA.entries(this, JarFileEntry::new); 522 } 523 524 /** 525 * Returns an ordered {@code Stream} over the jar file entries. 526 * Entries appear in the {@code Stream} in the order they appear in 527 * the central directory of the jar file. 528 * 529 * @return an ordered {@code Stream} of entries in this jar file 530 * @throws IllegalStateException if the jar file has been closed 531 * @since 1.8 532 */ 533 public Stream<JarEntry> stream() { 534 return JUZFA.stream(this, JarFileEntry::new); 535 } 536 537 /** 538 * Returns a {@code Stream} of the versioned jar file entries. 539 * 540 * <p>If this {@code JarFile} is a multi-release jar file and is configured to 541 * be processed as such, then an entry in the stream is the latest versioned entry 542 * associated with the corresponding base entry name. The maximum version of the 543 * latest versioned entry is the version returned by {@link #getVersion()}. 544 * The returned stream may include an entry that only exists as a versioned entry. 545 * 546 * If the jar file is not a multi-release jar file or the {@code JarFile} is not 547 * configured for processing a multi-release jar file, this method returns the 548 * same stream that {@link #stream()} returns. 549 * 550 * @return stream of versioned entries 551 * @since 10 552 */ 553 public Stream<JarEntry> versionedStream() { 554 555 if (isMultiRelease()) { 556 return JUZFA.entryNameStream(this).map(this::getBasename) 557 .filter(Objects::nonNull) 558 .distinct() 559 .map(this::getJarEntry) 560 .filter(Objects::nonNull); 561 } 562 return stream(); 563 } 564 565 /* 566 * Invokes {@ZipFile}'s getEntry to Return a {@code JarFileEntry} for the 567 * given entry name or {@code null} if not found. 568 */ 569 private JarFileEntry getEntry0(String name) { 570 // Not using a lambda/method reference here to optimize startup time 571 Function<String, JarEntry> newJarFileEntryFn = new Function<>() { 572 @Override 573 public JarEntry apply(String name) { 574 return new JarFileEntry(name); 575 } 576 }; 577 return (JarFileEntry)JUZFA.getEntry(this, name, newJarFileEntryFn); 578 } 579 580 private String getBasename(String name) { 581 if (name.startsWith(META_INF_VERSIONS)) { 582 int off = META_INF_VERSIONS.length(); 583 int index = name.indexOf('/', off); 584 try { 585 // filter out dir META-INF/versions/ and META-INF/versions/*/ 586 // and any entry with version > 'version' 587 if (index == -1 || index == (name.length() - 1) || 588 Integer.parseInt(name, off, index, 10) > versionFeature) { 589 return null; 590 } 591 } catch (NumberFormatException x) { 592 return null; // remove malformed entries silently 593 } 594 // map to its base name 595 return name.substring(index + 1); 596 } 597 return name; 598 } 599 600 private JarEntry getVersionedEntry(String name, JarEntry je) { 601 if (BASE_VERSION_FEATURE < versionFeature) { 602 if (!name.startsWith(META_INF)) { 603 // search for versioned entry 604 int v = versionFeature; 605 while (v > BASE_VERSION_FEATURE) { 606 JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name); 607 if (vje != null) { 608 return vje.withBasename(name); 609 } 610 v--; 611 } 612 } 613 } 614 return je; 615 } 616 617 // placeholder for now 618 String getRealName(JarEntry entry) { 619 return entry.getRealName(); 620 } 621 622 private class JarFileEntry extends JarEntry { 623 private String basename; 624 625 JarFileEntry(String name) { 626 super(name); 627 this.basename = name; 628 } 629 630 JarFileEntry(String name, ZipEntry vze) { 631 super(vze); 632 this.basename = name; 633 } 634 635 @Override 636 public Attributes getAttributes() throws IOException { 637 Manifest man = JarFile.this.getManifest(); 638 if (man != null) { 639 return man.getAttributes(super.getName()); 640 } else { 641 return null; 642 } 643 } 644 645 @Override 646 public Certificate[] getCertificates() { 647 try { 648 maybeInstantiateVerifier(); 649 } catch (IOException e) { 650 throw new RuntimeException(e); 651 } 652 if (certs == null && jv != null) { 653 certs = jv.getCerts(JarFile.this, realEntry()); 654 } 655 return certs == null ? null : certs.clone(); 656 } 657 658 @Override 659 public CodeSigner[] getCodeSigners() { 660 try { 661 maybeInstantiateVerifier(); 662 } catch (IOException e) { 663 throw new RuntimeException(e); 664 } 665 if (signers == null && jv != null) { 666 signers = jv.getCodeSigners(JarFile.this, realEntry()); 667 } 668 return signers == null ? null : signers.clone(); 669 } 670 671 @Override 672 public String getRealName() { 673 return super.getName(); 674 } 675 676 @Override 677 public String getName() { 678 return basename; 679 } 680 681 JarFileEntry realEntry() { 682 if (isMultiRelease() && versionFeature != BASE_VERSION_FEATURE) { 683 String entryName = super.getName(); 684 return entryName == basename || entryName.equals(basename) ? 685 this : new JarFileEntry(entryName, this); 686 } 687 return this; 688 } 689 690 // changes the basename, returns "this" 691 JarFileEntry withBasename(String name) { 692 basename = name; 693 return this; 694 } 695 } 696 697 /* 698 * Ensures that the JarVerifier has been created if one is 699 * necessary (i.e., the jar appears to be signed.) This is done as 700 * a quick check to avoid processing of the manifest for unsigned 701 * jars. 702 */ 703 private void maybeInstantiateVerifier() throws IOException { 704 if (jv != null) { 705 return; 706 } 707 708 if (verify) { 709 String[] names = getMetaInfEntryNames(); 710 if (names != null) { 711 for (String nameLower : names) { 712 String name = nameLower.toUpperCase(Locale.ENGLISH); 713 if (name.endsWith(".DSA") || 714 name.endsWith(".RSA") || 715 name.endsWith(".EC") || 716 name.endsWith(".SF")) { 717 // Assume since we found a signature-related file 718 // that the jar is signed and that we therefore 719 // need a JarVerifier and Manifest 720 getManifest(); 721 return; 722 } 723 } 724 } 725 // No signature-related files; don't instantiate a 726 // verifier 727 verify = false; 728 } 729 } 730 731 /* 732 * Initializes the verifier object by reading all the manifest 733 * entries and passing them to the verifier. 734 */ 735 private void initializeVerifier() { 736 ManifestEntryVerifier mev = null; 737 738 // Verify "META-INF/" entries... 739 try { 740 String[] names = getMetaInfEntryNames(); 741 if (names != null) { 742 for (String name : names) { 743 String uname = name.toUpperCase(Locale.ENGLISH); 744 if (MANIFEST_NAME.equals(uname) 745 || SignatureFileVerifier.isBlockOrSF(uname)) { 746 JarEntry e = getJarEntry(name); 747 if (e == null) { 748 throw new JarException("corrupted jar file"); 749 } 750 if (mev == null) { 751 mev = new ManifestEntryVerifier 752 (getManifestFromReference()); 753 } 754 byte[] b = getBytes(e); 755 if (b != null && b.length > 0) { 756 jv.beginEntry(e, mev); 757 jv.update(b.length, b, 0, b.length, mev); 758 jv.update(-1, null, 0, 0, mev); 759 } 760 } 761 } 762 } 763 } catch (IOException ex) { 764 // if we had an error parsing any blocks, just 765 // treat the jar file as being unsigned 766 jv = null; 767 verify = false; 768 if (JarVerifier.debug != null) { 769 JarVerifier.debug.println("jarfile parsing error!"); 770 ex.printStackTrace(); 771 } 772 } 773 774 // if after initializing the verifier we have nothing 775 // signed, we null it out. 776 777 if (jv != null) { 778 779 jv.doneWithMeta(); 780 if (JarVerifier.debug != null) { 781 JarVerifier.debug.println("done with meta!"); 782 } 783 784 if (jv.nothingToVerify()) { 785 if (JarVerifier.debug != null) { 786 JarVerifier.debug.println("nothing to verify!"); 787 } 788 jv = null; 789 verify = false; 790 } 791 } 792 } 793 794 /* 795 * Reads all the bytes for a given entry. Used to process the 796 * META-INF files. 797 */ 798 private byte[] getBytes(ZipEntry ze) throws IOException { 799 try (InputStream is = super.getInputStream(ze)) { 800 int len = (int)ze.getSize(); 801 int bytesRead; 802 byte[] b; 803 // trust specified entry sizes when reasonably small 804 if (len != -1 && len <= 65535) { 805 b = new byte[len]; 806 bytesRead = is.readNBytes(b, 0, len); 807 } else { 808 b = is.readAllBytes(); 809 bytesRead = b.length; 810 } 811 if (len != -1 && len != bytesRead) { 812 throw new EOFException("Expected:" + len + ", read:" + bytesRead); 813 } 814 return b; 815 } 816 } 817 818 /** 819 * Returns an input stream for reading the contents of the specified 820 * zip file entry. 821 * @param ze the zip file entry 822 * @return an input stream for reading the contents of the specified 823 * zip file entry 824 * @throws ZipException if a zip file format error has occurred 825 * @throws IOException if an I/O error has occurred 826 * @throws SecurityException if any of the jar file entries 827 * are incorrectly signed. 828 * @throws IllegalStateException 829 * may be thrown if the jar file has been closed 830 */ 831 public synchronized InputStream getInputStream(ZipEntry ze) 832 throws IOException 833 { 834 maybeInstantiateVerifier(); 835 if (jv == null) { 836 return super.getInputStream(ze); 837 } 838 if (!jvInitialized) { 839 initializeVerifier(); 840 jvInitialized = true; 841 // could be set to null after a call to 842 // initializeVerifier if we have nothing to 843 // verify 844 if (jv == null) 845 return super.getInputStream(ze); 846 } 847 848 // wrap a verifier stream around the real stream 849 return new JarVerifier.VerifierStream( 850 getManifestFromReference(), 851 verifiableEntry(ze), 852 super.getInputStream(ze), 853 jv); 854 } 855 856 private JarEntry verifiableEntry(ZipEntry ze) { 857 if (ze instanceof JarFileEntry) { 858 // assure the name and entry match for verification 859 return ((JarFileEntry)ze).realEntry(); 860 } 861 ze = getJarEntry(ze.getName()); 862 if (ze instanceof JarFileEntry) { 863 return ((JarFileEntry)ze).realEntry(); 864 } 865 return (JarEntry)ze; 866 } 867 868 // Statics for hand-coded Boyer-Moore search 869 private static final byte[] CLASSPATH_CHARS = 870 {'C','L','A','S','S','-','P','A','T','H', ':', ' '}; 871 872 // The bad character shift for "class-path: " 873 private static final byte[] CLASSPATH_LASTOCC; 874 875 // The good suffix shift for "class-path: " 876 private static final byte[] CLASSPATH_OPTOSFT; 877 878 private static final byte[] MULTIRELEASE_CHARS = 879 {'M','U','L','T','I','-','R','E','L','E', 'A', 'S', 'E', ':', 880 ' ', 'T', 'R', 'U', 'E'}; 881 882 // The bad character shift for "multi-release: true" 883 private static final byte[] MULTIRELEASE_LASTOCC; 884 885 // The good suffix shift for "multi-release: true" 886 private static final byte[] MULTIRELEASE_OPTOSFT; 887 888 static { 889 CLASSPATH_LASTOCC = new byte[65]; 890 CLASSPATH_OPTOSFT = new byte[12]; 891 CLASSPATH_LASTOCC[(int)'C' - 32] = 1; 892 CLASSPATH_LASTOCC[(int)'L' - 32] = 2; 893 CLASSPATH_LASTOCC[(int)'S' - 32] = 5; 894 CLASSPATH_LASTOCC[(int)'-' - 32] = 6; 895 CLASSPATH_LASTOCC[(int)'P' - 32] = 7; 896 CLASSPATH_LASTOCC[(int)'A' - 32] = 8; 897 CLASSPATH_LASTOCC[(int)'T' - 32] = 9; 898 CLASSPATH_LASTOCC[(int)'H' - 32] = 10; 899 CLASSPATH_LASTOCC[(int)':' - 32] = 11; 900 CLASSPATH_LASTOCC[(int)' ' - 32] = 12; 901 for (int i = 0; i < 11; i++) { 902 CLASSPATH_OPTOSFT[i] = 12; 903 } 904 CLASSPATH_OPTOSFT[11] = 1; 905 906 MULTIRELEASE_LASTOCC = new byte[65]; 907 MULTIRELEASE_OPTOSFT = new byte[19]; 908 MULTIRELEASE_LASTOCC[(int)'M' - 32] = 1; 909 MULTIRELEASE_LASTOCC[(int)'I' - 32] = 5; 910 MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6; 911 MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9; 912 MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11; 913 MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12; 914 MULTIRELEASE_LASTOCC[(int)':' - 32] = 14; 915 MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15; 916 MULTIRELEASE_LASTOCC[(int)'T' - 32] = 16; 917 MULTIRELEASE_LASTOCC[(int)'R' - 32] = 17; 918 MULTIRELEASE_LASTOCC[(int)'U' - 32] = 18; 919 MULTIRELEASE_LASTOCC[(int)'E' - 32] = 19; 920 for (int i = 0; i < 17; i++) { 921 MULTIRELEASE_OPTOSFT[i] = 19; 922 } 923 MULTIRELEASE_OPTOSFT[17] = 6; 924 MULTIRELEASE_OPTOSFT[18] = 1; 925 } 926 927 private JarEntry getManEntry() { 928 if (manEntry == null) { 929 // First look up manifest entry using standard name 930 JarEntry manEntry = getEntry0(MANIFEST_NAME); 931 if (manEntry == null) { 932 // If not found, then iterate through all the "META-INF/" 933 // entries to find a match. 934 String[] names = getMetaInfEntryNames(); 935 if (names != null) { 936 for (String name : names) { 937 if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) { 938 manEntry = getEntry0(name); 939 break; 940 } 941 } 942 } 943 } 944 this.manEntry = manEntry; 945 } 946 return manEntry; 947 } 948 949 /** 950 * Returns {@code true} iff this JAR file has a manifest with the 951 * Class-Path attribute 952 */ 953 boolean hasClassPathAttribute() throws IOException { 954 checkForSpecialAttributes(); 955 return hasClassPathAttribute; 956 } 957 958 /** 959 * Returns true if the pattern {@code src} is found in {@code b}. 960 * The {@code lastOcc} array is the precomputed bad character shifts. 961 * Since there are no repeated substring in our search strings, 962 * the good suffix shifts can be replaced with a comparison. 963 */ 964 private int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) { 965 int len = src.length; 966 int last = b.length - len; 967 int i = 0; 968 next: 969 while (i <= last) { 970 for (int j = (len - 1); j >= 0; j--) { 971 byte c = b[i + j]; 972 if (c >= ' ' && c <= 'z') { 973 if (c >= 'a') c -= 32; // Canonicalize 974 975 if (c != src[j]) { 976 // no match 977 int badShift = lastOcc[c - 32]; 978 i += Math.max(j + 1 - badShift, optoSft[j]); 979 continue next; 980 } 981 } else { 982 // no match, character not valid for name 983 i += len; 984 continue next; 985 } 986 } 987 return i; 988 } 989 return -1; 990 } 991 992 /** 993 * On first invocation, check if the JAR file has the Class-Path 994 * and the Multi-Release attribute. A no-op on subsequent calls. 995 */ 996 private void checkForSpecialAttributes() throws IOException { 997 if (hasCheckedSpecialAttributes) { 998 return; 999 } 1000 synchronized (this) { 1001 if (hasCheckedSpecialAttributes) { 1002 return; 1003 } 1004 JarEntry manEntry = getManEntry(); 1005 if (manEntry != null) { 1006 byte[] b = getBytes(manEntry); 1007 hasClassPathAttribute = match(CLASSPATH_CHARS, b, 1008 CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT) != -1; 1009 // is this a multi-release jar file 1010 if (MULTI_RELEASE_ENABLED) { 1011 int i = match(MULTIRELEASE_CHARS, b, MULTIRELEASE_LASTOCC, 1012 MULTIRELEASE_OPTOSFT); 1013 if (i != -1) { 1014 // Read the main attributes of the manifest 1015 byte[] lbuf = new byte[512]; 1016 Attributes attr = new Attributes(); 1017 attr.read(new Manifest.FastInputStream( 1018 new ByteArrayInputStream(b)), lbuf); 1019 isMultiRelease = Boolean.parseBoolean( 1020 attr.getValue(Attributes.Name.MULTI_RELEASE)); 1021 } 1022 } 1023 } 1024 hasCheckedSpecialAttributes = true; 1025 } 1026 } 1027 1028 synchronized void ensureInitialization() { 1029 try { 1030 maybeInstantiateVerifier(); 1031 } catch (IOException e) { 1032 throw new RuntimeException(e); 1033 } 1034 if (jv != null && !jvInitialized) { 1035 initializeVerifier(); 1036 jvInitialized = true; 1037 } 1038 } 1039 1040 /* 1041 * Returns a versioned {@code JarFileEntry} for the given entry, 1042 * if there is one. Otherwise returns the original entry. This 1043 * is invoked by the {@code entries2} for verifier. 1044 */ 1045 JarEntry newEntry(JarEntry je) { 1046 if (isMultiRelease()) { 1047 return getVersionedEntry(je.getName(), je); 1048 } 1049 return je; 1050 } 1051 1052 /* 1053 * Returns a versioned {@code JarFileEntry} for the given entry 1054 * name, if there is one. Otherwise returns a {@code JarFileEntry} 1055 * with the given name. It is invoked from JarVerifier's entries2 1056 * for {@code singers}. 1057 */ 1058 JarEntry newEntry(String name) { 1059 if (isMultiRelease()) { 1060 JarEntry vje = getVersionedEntry(name, (JarEntry)null); 1061 if (vje != null) { 1062 return vje; 1063 } 1064 } 1065 return new JarFileEntry(name); 1066 } 1067 1068 Enumeration<String> entryNames(CodeSource[] cs) { 1069 ensureInitialization(); 1070 if (jv != null) { 1071 return jv.entryNames(this, cs); 1072 } 1073 1074 /* 1075 * JAR file has no signed content. Is there a non-signing 1076 * code source? 1077 */ 1078 boolean includeUnsigned = false; 1079 for (CodeSource c : cs) { 1080 if (c.getCodeSigners() == null) { 1081 includeUnsigned = true; 1082 break; 1083 } 1084 } 1085 if (includeUnsigned) { 1086 return unsignedEntryNames(); 1087 } else { 1088 return Collections.emptyEnumeration(); 1089 } 1090 } 1091 1092 /** 1093 * Returns an enumeration of the zip file entries 1094 * excluding internal JAR mechanism entries and including 1095 * signed entries missing from the ZIP directory. 1096 */ 1097 Enumeration<JarEntry> entries2() { 1098 ensureInitialization(); 1099 if (jv != null) { 1100 return jv.entries2(this, JUZFA.entries(JarFile.this, 1101 JarFileEntry::new)); 1102 } 1103 1104 // screen out entries which are never signed 1105 final var unfilteredEntries = JUZFA.entries(JarFile.this, JarFileEntry::new); 1106 1107 return new Enumeration<>() { 1108 1109 JarEntry entry; 1110 1111 public boolean hasMoreElements() { 1112 if (entry != null) { 1113 return true; 1114 } 1115 while (unfilteredEntries.hasMoreElements()) { 1116 JarEntry je = unfilteredEntries.nextElement(); 1117 if (JarVerifier.isSigningRelated(je.getName())) { 1118 continue; 1119 } 1120 entry = je; 1121 return true; 1122 } 1123 return false; 1124 } 1125 1126 public JarEntry nextElement() { 1127 if (hasMoreElements()) { 1128 JarEntry je = entry; 1129 entry = null; 1130 return newEntry(je); 1131 } 1132 throw new NoSuchElementException(); 1133 } 1134 }; 1135 } 1136 1137 CodeSource[] getCodeSources(URL url) { 1138 ensureInitialization(); 1139 if (jv != null) { 1140 return jv.getCodeSources(this, url); 1141 } 1142 1143 /* 1144 * JAR file has no signed content. Is there a non-signing 1145 * code source? 1146 */ 1147 Enumeration<String> unsigned = unsignedEntryNames(); 1148 if (unsigned.hasMoreElements()) { 1149 return new CodeSource[]{JarVerifier.getUnsignedCS(url)}; 1150 } else { 1151 return null; 1152 } 1153 } 1154 1155 private Enumeration<String> unsignedEntryNames() { 1156 final Enumeration<JarEntry> entries = entries(); 1157 return new Enumeration<>() { 1158 1159 String name; 1160 1161 /* 1162 * Grab entries from ZIP directory but screen out 1163 * metadata. 1164 */ 1165 public boolean hasMoreElements() { 1166 if (name != null) { 1167 return true; 1168 } 1169 while (entries.hasMoreElements()) { 1170 String value; 1171 ZipEntry e = entries.nextElement(); 1172 value = e.getName(); 1173 if (e.isDirectory() || JarVerifier.isSigningRelated(value)) { 1174 continue; 1175 } 1176 name = value; 1177 return true; 1178 } 1179 return false; 1180 } 1181 1182 public String nextElement() { 1183 if (hasMoreElements()) { 1184 String value = name; 1185 name = null; 1186 return value; 1187 } 1188 throw new NoSuchElementException(); 1189 } 1190 }; 1191 } 1192 1193 CodeSource getCodeSource(URL url, String name) { 1194 ensureInitialization(); 1195 if (jv != null) { 1196 if (jv.eagerValidation) { 1197 CodeSource cs = null; 1198 JarEntry je = getJarEntry(name); 1199 if (je != null) { 1200 cs = jv.getCodeSource(url, this, je); 1201 } else { 1202 cs = jv.getCodeSource(url, name); 1203 } 1204 return cs; 1205 } else { 1206 return jv.getCodeSource(url, name); 1207 } 1208 } 1209 1210 return JarVerifier.getUnsignedCS(url); 1211 } 1212 1213 void setEagerValidation(boolean eager) { 1214 try { 1215 maybeInstantiateVerifier(); 1216 } catch (IOException e) { 1217 throw new RuntimeException(e); 1218 } 1219 if (jv != null) { 1220 jv.setEagerValidation(eager); 1221 } 1222 } 1223 1224 List<Object> getManifestDigests() { 1225 ensureInitialization(); 1226 if (jv != null) { 1227 return jv.getManifestDigests(); 1228 } 1229 return new ArrayList<>(); 1230 } 1231 }