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.misc.SharedSecrets; 29 import jdk.internal.misc.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 = jdk.internal.misc.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 man = new Manifest(new ByteArrayInputStream(b), getName()); 421 if (!jvInitialized) { 422 jv = new JarVerifier(b); 423 } 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 } 561 return stream(); 562 } 563 564 /* 565 * Invokes {@ZipFile}'s getEntry to Return a {@code JarFileEntry} for the 566 * given entry name or {@code null} if not found. 567 */ 568 private JarFileEntry getEntry0(String name) { 569 // Not using a lambda/method reference here to optimize startup time 570 Function<String, JarEntry> newJarFileEntryFn = new Function<>() { 571 @Override 572 public JarEntry apply(String name) { 573 return new JarFileEntry(name); 574 } 575 }; 576 return (JarFileEntry)JUZFA.getEntry(this, name, newJarFileEntryFn); 577 } 578 579 private String getBasename(String name) { 580 if (name.startsWith(META_INF_VERSIONS)) { 581 int off = META_INF_VERSIONS.length(); 582 int index = name.indexOf('/', off); 583 try { 584 // filter out dir META-INF/versions/ and META-INF/versions/*/ 585 // and any entry with version > 'version' 586 if (index == -1 || index == (name.length() - 1) || 587 Integer.parseInt(name, off, index, 10) > versionFeature) { 588 return null; 589 } 590 } catch (NumberFormatException x) { 591 return null; // remove malformed entries silently 592 } 593 // map to its base name 594 return name.substring(index + 1); 595 } 596 return name; 597 } 598 599 private JarEntry getVersionedEntry(String name, JarEntry je) { 600 if (BASE_VERSION_FEATURE < versionFeature) { 601 if (!name.startsWith(META_INF)) { 602 // search for versioned entry 603 int v = versionFeature; 604 while (v > BASE_VERSION_FEATURE) { 605 JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name); 606 if (vje != null) { 607 return vje.withBasename(name); 608 } 609 v--; 610 } 611 } 612 } 613 return je; 614 } 615 616 // placeholder for now 617 String getRealName(JarEntry entry) { 618 return entry.getRealName(); 619 } 620 621 private class JarFileEntry extends JarEntry { 622 private String basename; 623 624 JarFileEntry(String name) { 625 super(name); 626 this.basename = name; 627 } 628 629 JarFileEntry(String name, ZipEntry vze) { 630 super(vze); 631 this.basename = name; 632 } 633 634 @Override 635 public Attributes getAttributes() throws IOException { 636 Manifest man = JarFile.this.getManifest(); 637 if (man != null) { 638 return man.getAttributes(super.getName()); 639 } else { 640 return null; 641 } 642 } 643 644 @Override 645 public Certificate[] getCertificates() { 646 try { 647 maybeInstantiateVerifier(); 648 } catch (IOException e) { 649 throw new RuntimeException(e); 650 } 651 if (certs == null && jv != null) { 652 certs = jv.getCerts(JarFile.this, realEntry()); 653 } 654 return certs == null ? null : certs.clone(); 655 } 656 657 @Override 658 public CodeSigner[] getCodeSigners() { 659 try { 660 maybeInstantiateVerifier(); 661 } catch (IOException e) { 662 throw new RuntimeException(e); 663 } 664 if (signers == null && jv != null) { 665 signers = jv.getCodeSigners(JarFile.this, realEntry()); 666 } 667 return signers == null ? null : signers.clone(); 668 } 669 670 @Override 671 public String getRealName() { 672 return super.getName(); 673 } 674 675 @Override 676 public String getName() { 677 return basename; 678 } 679 680 JarFileEntry realEntry() { 681 if (isMultiRelease() && versionFeature != BASE_VERSION_FEATURE) { 682 String entryName = super.getName(); 683 return entryName == basename || entryName.equals(basename) ? 684 this : new JarFileEntry(entryName, this); 685 } 686 return this; 687 } 688 689 // changes the basename, returns "this" 690 JarFileEntry withBasename(String name) { 691 basename = name; 692 return this; 693 } 694 } 695 696 /* 697 * Ensures that the JarVerifier has been created if one is 698 * necessary (i.e., the jar appears to be signed.) This is done as 699 * a quick check to avoid processing of the manifest for unsigned 700 * jars. 701 */ 702 private void maybeInstantiateVerifier() throws IOException { 703 if (jv != null) { 704 return; 705 } 706 707 if (verify) { 708 String[] names = getMetaInfEntryNames(); 709 if (names != null) { 710 for (String nameLower : names) { 711 String name = nameLower.toUpperCase(Locale.ENGLISH); 712 if (name.endsWith(".DSA") || 713 name.endsWith(".RSA") || 714 name.endsWith(".EC") || 715 name.endsWith(".SF")) { 716 // Assume since we found a signature-related file 717 // that the jar is signed and that we therefore 718 // need a JarVerifier and Manifest 719 getManifest(); 720 return; 721 } 722 } 723 } 724 // No signature-related files; don't instantiate a 725 // verifier 726 verify = false; 727 } 728 } 729 730 /* 731 * Initializes the verifier object by reading all the manifest 732 * entries and passing them to the verifier. 733 */ 734 private void initializeVerifier() { 735 ManifestEntryVerifier mev = null; 736 737 // Verify "META-INF/" entries... 738 try { 739 String[] names = getMetaInfEntryNames(); 740 if (names != null) { 741 for (String name : names) { 742 String uname = name.toUpperCase(Locale.ENGLISH); 743 if (MANIFEST_NAME.equals(uname) 744 || SignatureFileVerifier.isBlockOrSF(uname)) { 745 JarEntry e = getJarEntry(name); 746 if (e == null) { 747 throw new JarException("corrupted jar file"); 748 } 749 if (mev == null) { 750 mev = new ManifestEntryVerifier 751 (getManifestFromReference()); 752 } 753 byte[] b = getBytes(e); 754 if (b != null && b.length > 0) { 755 jv.beginEntry(e, mev); 756 jv.update(b.length, b, 0, b.length, mev); 757 jv.update(-1, null, 0, 0, mev); 758 } 759 } 760 } 761 } 762 } catch (IOException ex) { 763 // if we had an error parsing any blocks, just 764 // treat the jar file as being unsigned 765 jv = null; 766 verify = false; 767 if (JarVerifier.debug != null) { 768 JarVerifier.debug.println("jarfile parsing error!"); 769 ex.printStackTrace(); 770 } 771 } 772 773 // if after initializing the verifier we have nothing 774 // signed, we null it out. 775 776 if (jv != null) { 777 778 jv.doneWithMeta(); 779 if (JarVerifier.debug != null) { 780 JarVerifier.debug.println("done with meta!"); 781 } 782 783 if (jv.nothingToVerify()) { 784 if (JarVerifier.debug != null) { 785 JarVerifier.debug.println("nothing to verify!"); 786 } 787 jv = null; 788 verify = false; 789 } 790 } 791 } 792 793 /* 794 * Reads all the bytes for a given entry. Used to process the 795 * META-INF files. 796 */ 797 private byte[] getBytes(ZipEntry ze) throws IOException { 798 try (InputStream is = super.getInputStream(ze)) { 799 int len = (int)ze.getSize(); 800 int bytesRead; 801 byte[] b; 802 // trust specified entry sizes when reasonably small 803 if (len != -1 && len <= 65535) { 804 b = new byte[len]; 805 bytesRead = is.readNBytes(b, 0, len); 806 } else { 807 b = is.readAllBytes(); 808 bytesRead = b.length; 809 } 810 if (len != -1 && len != bytesRead) { 811 throw new EOFException("Expected:" + len + ", read:" + bytesRead); 812 } 813 return b; 814 } 815 } 816 817 /** 818 * Returns an input stream for reading the contents of the specified 819 * zip file entry. 820 * @param ze the zip file entry 821 * @return an input stream for reading the contents of the specified 822 * zip file entry 823 * @throws ZipException if a zip file format error has occurred 824 * @throws IOException if an I/O error has occurred 825 * @throws SecurityException if any of the jar file entries 826 * are incorrectly signed. 827 * @throws IllegalStateException 828 * may be thrown if the jar file has been closed 829 */ 830 public synchronized InputStream getInputStream(ZipEntry ze) 831 throws IOException 832 { 833 maybeInstantiateVerifier(); 834 if (jv == null) { 835 return super.getInputStream(ze); 836 } 837 if (!jvInitialized) { 838 initializeVerifier(); 839 jvInitialized = true; 840 // could be set to null after a call to 841 // initializeVerifier if we have nothing to 842 // verify 843 if (jv == null) 844 return super.getInputStream(ze); 845 } 846 847 // wrap a verifier stream around the real stream 848 return new JarVerifier.VerifierStream( 849 getManifestFromReference(), 850 verifiableEntry(ze), 851 super.getInputStream(ze), 852 jv); 853 } 854 855 private JarEntry verifiableEntry(ZipEntry ze) { 856 if (ze instanceof JarFileEntry) { 857 // assure the name and entry match for verification 858 return ((JarFileEntry)ze).realEntry(); 859 } 860 ze = getJarEntry(ze.getName()); 861 if (ze instanceof JarFileEntry) { 862 return ((JarFileEntry)ze).realEntry(); 863 } 864 return (JarEntry)ze; 865 } 866 867 // Statics for hand-coded Boyer-Moore search 868 private static final byte[] CLASSPATH_CHARS = 869 {'C','L','A','S','S','-','P','A','T','H', ':', ' '}; 870 871 // The bad character shift for "class-path: " 872 private static final byte[] CLASSPATH_LASTOCC; 873 874 // The good suffix shift for "class-path: " 875 private static final byte[] CLASSPATH_OPTOSFT; 876 877 private static final byte[] MULTIRELEASE_CHARS = 878 {'M','U','L','T','I','-','R','E','L','E', 'A', 'S', 'E', ':', 879 ' ', 'T', 'R', 'U', 'E'}; 880 881 // The bad character shift for "multi-release: true" 882 private static final byte[] MULTIRELEASE_LASTOCC; 883 884 // The good suffix shift for "multi-release: true" 885 private static final byte[] MULTIRELEASE_OPTOSFT; 886 887 static { 888 CLASSPATH_LASTOCC = new byte[65]; 889 CLASSPATH_OPTOSFT = new byte[12]; 890 CLASSPATH_LASTOCC[(int)'C' - 32] = 1; 891 CLASSPATH_LASTOCC[(int)'L' - 32] = 2; 892 CLASSPATH_LASTOCC[(int)'S' - 32] = 5; 893 CLASSPATH_LASTOCC[(int)'-' - 32] = 6; 894 CLASSPATH_LASTOCC[(int)'P' - 32] = 7; 895 CLASSPATH_LASTOCC[(int)'A' - 32] = 8; 896 CLASSPATH_LASTOCC[(int)'T' - 32] = 9; 897 CLASSPATH_LASTOCC[(int)'H' - 32] = 10; 898 CLASSPATH_LASTOCC[(int)':' - 32] = 11; 899 CLASSPATH_LASTOCC[(int)' ' - 32] = 12; 900 for (int i = 0; i < 11; i++) { 901 CLASSPATH_OPTOSFT[i] = 12; 902 } 903 CLASSPATH_OPTOSFT[11] = 1; 904 905 MULTIRELEASE_LASTOCC = new byte[65]; 906 MULTIRELEASE_OPTOSFT = new byte[19]; 907 MULTIRELEASE_LASTOCC[(int)'M' - 32] = 1; 908 MULTIRELEASE_LASTOCC[(int)'I' - 32] = 5; 909 MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6; 910 MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9; 911 MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11; 912 MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12; 913 MULTIRELEASE_LASTOCC[(int)':' - 32] = 14; 914 MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15; 915 MULTIRELEASE_LASTOCC[(int)'T' - 32] = 16; 916 MULTIRELEASE_LASTOCC[(int)'R' - 32] = 17; 917 MULTIRELEASE_LASTOCC[(int)'U' - 32] = 18; 918 MULTIRELEASE_LASTOCC[(int)'E' - 32] = 19; 919 for (int i = 0; i < 17; i++) { 920 MULTIRELEASE_OPTOSFT[i] = 19; 921 } 922 MULTIRELEASE_OPTOSFT[17] = 6; 923 MULTIRELEASE_OPTOSFT[18] = 1; 924 } 925 926 private JarEntry getManEntry() { 927 if (manEntry == null) { 928 // First look up manifest entry using standard name 929 JarEntry manEntry = getEntry0(MANIFEST_NAME); 930 if (manEntry == null) { 931 // If not found, then iterate through all the "META-INF/" 932 // entries to find a match. 933 String[] names = getMetaInfEntryNames(); 934 if (names != null) { 935 for (String name : names) { 936 if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) { 937 manEntry = getEntry0(name); 938 break; 939 } 940 } 941 } 942 } 943 this.manEntry = manEntry; 944 } 945 return manEntry; 946 } 947 948 /** 949 * Returns {@code true} iff this JAR file has a manifest with the 950 * Class-Path attribute 951 */ 952 boolean hasClassPathAttribute() throws IOException { 953 checkForSpecialAttributes(); 954 return hasClassPathAttribute; 955 } 956 957 /** 958 * Returns true if the pattern {@code src} is found in {@code b}. 959 * The {@code lastOcc} array is the precomputed bad character shifts. 960 * Since there are no repeated substring in our search strings, 961 * the good suffix shifts can be replaced with a comparison. 962 */ 963 private int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) { 964 int len = src.length; 965 int last = b.length - len; 966 int i = 0; 967 next: 968 while (i <= last) { 969 for (int j = (len - 1); j >= 0; j--) { 970 byte c = b[i + j]; 971 if (c >= ' ' && c <= 'z') { 972 if (c >= 'a') c -= 32; // Canonicalize 973 974 if (c != src[j]) { 975 // no match 976 int badShift = lastOcc[c - 32]; 977 i += Math.max(j + 1 - badShift, optoSft[j]); 978 continue next; 979 } 980 } else { 981 // no match, character not valid for name 982 i += len; 983 continue next; 984 } 985 } 986 return i; 987 } 988 return -1; 989 } 990 991 /** 992 * On first invocation, check if the JAR file has the Class-Path 993 * and the Multi-Release attribute. A no-op on subsequent calls. 994 */ 995 private void checkForSpecialAttributes() throws IOException { 996 if (hasCheckedSpecialAttributes) { 997 return; 998 } 999 synchronized (this) { 1000 if (hasCheckedSpecialAttributes) { 1001 return; 1002 } 1003 JarEntry manEntry = getManEntry(); 1004 if (manEntry != null) { 1005 byte[] b = getBytes(manEntry); 1006 hasClassPathAttribute = match(CLASSPATH_CHARS, b, 1007 CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT) != -1; 1008 // is this a multi-release jar file 1009 if (MULTI_RELEASE_ENABLED) { 1010 int i = match(MULTIRELEASE_CHARS, b, MULTIRELEASE_LASTOCC, 1011 MULTIRELEASE_OPTOSFT); 1012 if (i != -1) { 1013 i += MULTIRELEASE_CHARS.length; 1014 if (i < b.length) { 1015 byte c = b[i++]; 1016 // Check that the value is followed by a newline 1017 // and does not have a continuation 1018 if (c == '\n' && 1019 (i == b.length || b[i] != ' ')) { 1020 isMultiRelease = true; 1021 } else if (c == '\r') { 1022 if (i == b.length) { 1023 isMultiRelease = true; 1024 } else { 1025 c = b[i++]; 1026 if (c == '\n') { 1027 if (i == b.length || b[i] != ' ') { 1028 isMultiRelease = true; 1029 } 1030 } else if (c != ' ') { 1031 isMultiRelease = true; 1032 } 1033 } 1034 } 1035 } 1036 } 1037 } 1038 } 1039 hasCheckedSpecialAttributes = true; 1040 } 1041 } 1042 1043 private synchronized void ensureInitialization() { 1044 try { 1045 maybeInstantiateVerifier(); 1046 } catch (IOException e) { 1047 throw new RuntimeException(e); 1048 } 1049 if (jv != null && !jvInitialized) { 1050 initializeVerifier(); 1051 jvInitialized = true; 1052 } 1053 } 1054 1055 /* 1056 * Returns a versioned {@code JarFileEntry} for the given entry, 1057 * if there is one. Otherwise returns the original entry. This 1058 * is invoked by the {@code entries2} for verifier. 1059 */ 1060 JarEntry newEntry(JarEntry je) { 1061 if (isMultiRelease()) { 1062 return getVersionedEntry(je.getName(), je); 1063 } 1064 return je; 1065 } 1066 1067 /* 1068 * Returns a versioned {@code JarFileEntry} for the given entry 1069 * name, if there is one. Otherwise returns a {@code JarFileEntry} 1070 * with the given name. It is invoked from JarVerifier's entries2 1071 * for {@code singers}. 1072 */ 1073 JarEntry newEntry(String name) { 1074 if (isMultiRelease()) { 1075 JarEntry vje = getVersionedEntry(name, (JarEntry)null); 1076 if (vje != null) { 1077 return vje; 1078 } 1079 } 1080 return new JarFileEntry(name); 1081 } 1082 1083 Enumeration<String> entryNames(CodeSource[] cs) { 1084 ensureInitialization(); 1085 if (jv != null) { 1086 return jv.entryNames(this, cs); 1087 } 1088 1089 /* 1090 * JAR file has no signed content. Is there a non-signing 1091 * code source? 1092 */ 1093 boolean includeUnsigned = false; 1094 for (CodeSource c : cs) { 1095 if (c.getCodeSigners() == null) { 1096 includeUnsigned = true; 1097 break; 1098 } 1099 } 1100 if (includeUnsigned) { 1101 return unsignedEntryNames(); 1102 } else { 1103 return Collections.emptyEnumeration(); 1104 } 1105 } 1106 1107 /** 1108 * Returns an enumeration of the zip file entries 1109 * excluding internal JAR mechanism entries and including 1110 * signed entries missing from the ZIP directory. 1111 */ 1112 Enumeration<JarEntry> entries2() { 1113 ensureInitialization(); 1114 if (jv != null) { 1115 return jv.entries2(this, JUZFA.entries(JarFile.this, 1116 JarFileEntry::new)); 1117 } 1118 1119 // screen out entries which are never signed 1120 final var unfilteredEntries = JUZFA.entries(JarFile.this, JarFileEntry::new); 1121 1122 return new Enumeration<>() { 1123 1124 JarEntry entry; 1125 1126 public boolean hasMoreElements() { 1127 if (entry != null) { 1128 return true; 1129 } 1130 while (unfilteredEntries.hasMoreElements()) { 1131 JarEntry je = unfilteredEntries.nextElement(); 1132 if (JarVerifier.isSigningRelated(je.getName())) { 1133 continue; 1134 } 1135 entry = je; 1136 return true; 1137 } 1138 return false; 1139 } 1140 1141 public JarEntry nextElement() { 1142 if (hasMoreElements()) { 1143 JarEntry je = entry; 1144 entry = null; 1145 return newEntry(je); 1146 } 1147 throw new NoSuchElementException(); 1148 } 1149 }; 1150 } 1151 1152 CodeSource[] getCodeSources(URL url) { 1153 ensureInitialization(); 1154 if (jv != null) { 1155 return jv.getCodeSources(this, url); 1156 } 1157 1158 /* 1159 * JAR file has no signed content. Is there a non-signing 1160 * code source? 1161 */ 1162 Enumeration<String> unsigned = unsignedEntryNames(); 1163 if (unsigned.hasMoreElements()) { 1164 return new CodeSource[]{JarVerifier.getUnsignedCS(url)}; 1165 } else { 1166 return null; 1167 } 1168 } 1169 1170 private Enumeration<String> unsignedEntryNames() { 1171 final Enumeration<JarEntry> entries = entries(); 1172 return new Enumeration<>() { 1173 1174 String name; 1175 1176 /* 1177 * Grab entries from ZIP directory but screen out 1178 * metadata. 1179 */ 1180 public boolean hasMoreElements() { 1181 if (name != null) { 1182 return true; 1183 } 1184 while (entries.hasMoreElements()) { 1185 String value; 1186 ZipEntry e = entries.nextElement(); 1187 value = e.getName(); 1188 if (e.isDirectory() || JarVerifier.isSigningRelated(value)) { 1189 continue; 1190 } 1191 name = value; 1192 return true; 1193 } 1194 return false; 1195 } 1196 1197 public String nextElement() { 1198 if (hasMoreElements()) { 1199 String value = name; 1200 name = null; 1201 return value; 1202 } 1203 throw new NoSuchElementException(); 1204 } 1205 }; 1206 } 1207 1208 CodeSource getCodeSource(URL url, String name) { 1209 ensureInitialization(); 1210 if (jv != null) { 1211 if (jv.eagerValidation) { 1212 CodeSource cs = null; 1213 JarEntry je = getJarEntry(name); 1214 if (je != null) { 1215 cs = jv.getCodeSource(url, this, je); 1216 } else { 1217 cs = jv.getCodeSource(url, name); 1218 } 1219 return cs; 1220 } else { 1221 return jv.getCodeSource(url, name); 1222 } 1223 } 1224 1225 return JarVerifier.getUnsignedCS(url); 1226 } 1227 1228 void setEagerValidation(boolean eager) { 1229 try { 1230 maybeInstantiateVerifier(); 1231 } catch (IOException e) { 1232 throw new RuntimeException(e); 1233 } 1234 if (jv != null) { 1235 jv.setEagerValidation(eager); 1236 } 1237 } 1238 1239 List<Object> getManifestDigests() { 1240 ensureInitialization(); 1241 if (jv != null) { 1242 return jv.getManifestDigests(); 1243 } 1244 return new ArrayList<>(); 1245 } 1246 }