1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package java.util.jar; 28 29 import java.io.*; 30 import java.lang.ref.SoftReference; 31 import java.util.*; 32 import java.util.stream.Stream; 33 import java.util.stream.StreamSupport; 34 import java.util.zip.*; 35 import java.security.CodeSigner; 36 import java.security.cert.Certificate; 37 import java.security.AccessController; 38 import sun.misc.IOUtils; 39 import sun.security.action.GetPropertyAction; 40 import sun.security.util.ManifestEntryVerifier; 41 import sun.security.util.SignatureFileVerifier; 42 43 /** 44 * The <code>JarFile</code> class is used to read the contents of a jar file 45 * from any file that can be opened with <code>java.io.RandomAccessFile</code>. 46 * It extends the class <code>java.util.zip.ZipFile</code> with support 47 * for reading an optional <code>Manifest</code> entry. The 48 * <code>Manifest</code> can be used to specify meta-information about the 49 * jar file and its entries. 50 * 51 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor 52 * or method in this class will cause a {@link NullPointerException} to be 53 * thrown. 54 * 55 * If the verify flag is on when opening a signed jar file, the content of the 56 * file is verified against its signature embedded inside the file. Please note 57 * that the verification process does not include validating the signer's 58 * certificate. A caller should inspect the return value of 59 * {@link JarEntry#getCodeSigners()} to further determine if the signature 60 * can be trusted. 61 * 62 * @author David Connelly 63 * @see Manifest 64 * @see java.util.zip.ZipFile 65 * @see java.util.jar.JarEntry 66 * @since 1.2 67 */ 68 public 69 class JarFile extends ZipFile { 70 static final String META_DIR = "META-INF/"; 71 private Manifest manifest; 72 private JarEntry manEntry; 73 private JarVerifier jv; 74 private boolean jvInitialized; 75 private boolean verify; 76 77 // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true) 78 private boolean hasClassPathAttribute; 79 // true if manifest checked for special attributes 80 private volatile boolean hasCheckedSpecialAttributes; 81 82 /** 83 * The JAR manifest file name. 84 */ 85 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 86 87 /** 88 * Creates a new <code>JarFile</code> to read from the specified 89 * file <code>name</code>. The <code>JarFile</code> will be verified if 90 * it is signed. 91 * @param name the name of the jar file to be opened for reading 92 * @throws IOException if an I/O error has occurred 93 * @throws SecurityException if access to the file is denied 94 * by the SecurityManager 95 */ JarFile(String name)96 public JarFile(String name) throws IOException { 97 this(new File(name), true, ZipFile.OPEN_READ); 98 } 99 100 /** 101 * Creates a new <code>JarFile</code> to read from the specified 102 * file <code>name</code>. 103 * @param name the name of the jar file to be opened for reading 104 * @param verify whether or not to verify the jar file if 105 * it is signed. 106 * @throws IOException if an I/O error has occurred 107 * @throws SecurityException if access to the file is denied 108 * by the SecurityManager 109 */ JarFile(String name, boolean verify)110 public JarFile(String name, boolean verify) throws IOException { 111 this(new File(name), verify, ZipFile.OPEN_READ); 112 } 113 114 /** 115 * Creates a new <code>JarFile</code> to read from the specified 116 * <code>File</code> object. The <code>JarFile</code> will be verified if 117 * it is signed. 118 * @param file the jar file to be opened for reading 119 * @throws IOException if an I/O error has occurred 120 * @throws SecurityException if access to the file is denied 121 * by the SecurityManager 122 */ JarFile(File file)123 public JarFile(File file) throws IOException { 124 this(file, true, ZipFile.OPEN_READ); 125 } 126 127 128 /** 129 * Creates a new <code>JarFile</code> to read from the specified 130 * <code>File</code> object. 131 * @param file the jar file to be opened for reading 132 * @param verify whether or not to verify the jar file if 133 * it is signed. 134 * @throws IOException if an I/O error has occurred 135 * @throws SecurityException if access to the file is denied 136 * by the SecurityManager. 137 */ JarFile(File file, boolean verify)138 public JarFile(File file, boolean verify) throws IOException { 139 this(file, verify, ZipFile.OPEN_READ); 140 } 141 142 143 /** 144 * Creates a new <code>JarFile</code> to read from the specified 145 * <code>File</code> object in the specified mode. The mode argument 146 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 147 * 148 * @param file the jar file to be opened for reading 149 * @param verify whether or not to verify the jar file if 150 * it is signed. 151 * @param mode the mode in which the file is to be opened 152 * @throws IOException if an I/O error has occurred 153 * @throws IllegalArgumentException 154 * if the <tt>mode</tt> argument is invalid 155 * @throws SecurityException if access to the file is denied 156 * by the SecurityManager 157 * @since 1.3 158 */ JarFile(File file, boolean verify, int mode)159 public JarFile(File file, boolean verify, int mode) throws IOException { 160 super(file, mode); 161 this.verify = verify; 162 } 163 164 /** 165 * Returns the jar file manifest, or <code>null</code> if none. 166 * 167 * @return the jar file manifest, or <code>null</code> if none 168 * 169 * @throws IllegalStateException 170 * may be thrown if the jar file has been closed 171 * @throws IOException if an I/O error has occurred 172 */ getManifest()173 public Manifest getManifest() throws IOException { 174 return getManifestFromReference(); 175 } 176 getManifestFromReference()177 private synchronized Manifest getManifestFromReference() throws IOException { 178 if (manifest == null) { 179 180 JarEntry manEntry = getManEntry(); 181 182 // If found then load the manifest 183 if (manEntry != null) { 184 if (verify) { 185 byte[] b = getBytes(manEntry); 186 manifest = new Manifest(new ByteArrayInputStream(b)); 187 if (!jvInitialized) { 188 jv = new JarVerifier(b); 189 } 190 } else { 191 manifest = new Manifest(super.getInputStream(manEntry)); 192 } 193 } 194 } 195 return manifest; 196 } 197 getMetaInfEntryNames()198 private native String[] getMetaInfEntryNames(); 199 200 /** 201 * Returns the <code>JarEntry</code> for the given entry name or 202 * <code>null</code> if not found. 203 * 204 * @param name the jar file entry name 205 * @return the <code>JarEntry</code> for the given entry name or 206 * <code>null</code> if not found. 207 * 208 * @throws IllegalStateException 209 * may be thrown if the jar file has been closed 210 * 211 * @see java.util.jar.JarEntry 212 */ getJarEntry(String name)213 public JarEntry getJarEntry(String name) { 214 return (JarEntry)getEntry(name); 215 } 216 217 /** 218 * Returns the <code>ZipEntry</code> for the given entry name or 219 * <code>null</code> if not found. 220 * 221 * @param name the jar file entry name 222 * @return the <code>ZipEntry</code> for the given entry name or 223 * <code>null</code> if not found 224 * 225 * @throws IllegalStateException 226 * may be thrown if the jar file has been closed 227 * 228 * @see java.util.zip.ZipEntry 229 */ getEntry(String name)230 public ZipEntry getEntry(String name) { 231 ZipEntry ze = super.getEntry(name); 232 if (ze != null) { 233 return new JarFileEntry(ze); 234 } 235 return null; 236 } 237 238 private class JarEntryIterator implements Enumeration<JarEntry>, 239 Iterator<JarEntry> 240 { 241 final Enumeration<? extends ZipEntry> e = JarFile.super.entries(); 242 hasNext()243 public boolean hasNext() { 244 return e.hasMoreElements(); 245 } 246 next()247 public JarEntry next() { 248 ZipEntry ze = e.nextElement(); 249 return new JarFileEntry(ze); 250 } 251 hasMoreElements()252 public boolean hasMoreElements() { 253 return hasNext(); 254 } 255 nextElement()256 public JarEntry nextElement() { 257 return next(); 258 } 259 } 260 261 /** 262 * Returns an enumeration of the zip file entries. 263 */ entries()264 public Enumeration<JarEntry> entries() { 265 return new JarEntryIterator(); 266 } 267 268 @Override stream()269 public Stream<JarEntry> stream() { 270 return StreamSupport.stream(Spliterators.spliterator( 271 new JarEntryIterator(), size(), 272 Spliterator.ORDERED | Spliterator.DISTINCT | 273 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); 274 } 275 276 private class JarFileEntry extends JarEntry { JarFileEntry(ZipEntry ze)277 JarFileEntry(ZipEntry ze) { 278 super(ze); 279 } getAttributes()280 public Attributes getAttributes() throws IOException { 281 Manifest man = JarFile.this.getManifest(); 282 if (man != null) { 283 return man.getAttributes(getName()); 284 } else { 285 return null; 286 } 287 } getCertificates()288 public Certificate[] getCertificates() { 289 try { 290 maybeInstantiateVerifier(); 291 } catch (IOException e) { 292 throw new RuntimeException(e); 293 } 294 if (certs == null && jv != null) { 295 certs = jv.getCerts(JarFile.this, this); 296 } 297 return certs == null ? null : certs.clone(); 298 } getCodeSigners()299 public CodeSigner[] getCodeSigners() { 300 try { 301 maybeInstantiateVerifier(); 302 } catch (IOException e) { 303 throw new RuntimeException(e); 304 } 305 if (signers == null && jv != null) { 306 signers = jv.getCodeSigners(JarFile.this, this); 307 } 308 return signers == null ? null : signers.clone(); 309 } 310 } 311 312 /* 313 * Ensures that the JarVerifier has been created if one is 314 * necessary (i.e., the jar appears to be signed.) This is done as 315 * a quick check to avoid processing of the manifest for unsigned 316 * jars. 317 */ maybeInstantiateVerifier()318 private void maybeInstantiateVerifier() throws IOException { 319 if (jv != null) { 320 return; 321 } 322 323 if (verify) { 324 String[] names = getMetaInfEntryNames(); 325 if (names != null) { 326 for (int i = 0; i < names.length; i++) { 327 String name = names[i].toUpperCase(Locale.ENGLISH); 328 if (name.endsWith(".DSA") || 329 name.endsWith(".RSA") || 330 name.endsWith(".EC") || 331 name.endsWith(".SF")) { 332 // Assume since we found a signature-related file 333 // that the jar is signed and that we therefore 334 // need a JarVerifier and Manifest 335 getManifest(); 336 return; 337 } 338 } 339 } 340 // No signature-related files; don't instantiate a 341 // verifier 342 verify = false; 343 } 344 } 345 346 347 /* 348 * Initializes the verifier object by reading all the manifest 349 * entries and passing them to the verifier. 350 */ initializeVerifier()351 private void initializeVerifier() { 352 ManifestEntryVerifier mev = null; 353 354 // Verify "META-INF/" entries... 355 try { 356 String[] names = getMetaInfEntryNames(); 357 if (names != null) { 358 for (int i = 0; i < names.length; i++) { 359 String uname = names[i].toUpperCase(Locale.ENGLISH); 360 if (MANIFEST_NAME.equals(uname) 361 || SignatureFileVerifier.isBlockOrSF(uname)) { 362 JarEntry e = getJarEntry(names[i]); 363 if (e == null) { 364 throw new JarException("corrupted jar file"); 365 } 366 if (mev == null) { 367 mev = new ManifestEntryVerifier 368 (getManifestFromReference()); 369 } 370 byte[] b = getBytes(e); 371 if (b != null && b.length > 0) { 372 jv.beginEntry(e, mev); 373 jv.update(b.length, b, 0, b.length, mev); 374 jv.update(-1, null, 0, 0, mev); 375 } 376 } 377 } 378 } 379 } catch (IOException ex) { 380 // if we had an error parsing any blocks, just 381 // treat the jar file as being unsigned 382 jv = null; 383 verify = false; 384 if (JarVerifier.debug != null) { 385 JarVerifier.debug.println("jarfile parsing error!"); 386 ex.printStackTrace(); 387 } 388 } 389 390 // if after initializing the verifier we have nothing 391 // signed, we null it out. 392 393 if (jv != null) { 394 395 jv.doneWithMeta(); 396 if (JarVerifier.debug != null) { 397 JarVerifier.debug.println("done with meta!"); 398 } 399 400 if (jv.nothingToVerify()) { 401 if (JarVerifier.debug != null) { 402 JarVerifier.debug.println("nothing to verify!"); 403 } 404 jv = null; 405 verify = false; 406 } 407 } 408 } 409 410 /* 411 * Reads all the bytes for a given entry. Used to process the 412 * META-INF files. 413 */ getBytes(ZipEntry ze)414 private byte[] getBytes(ZipEntry ze) throws IOException { 415 try (InputStream is = super.getInputStream(ze)) { 416 return IOUtils.readFully(is, (int)ze.getSize(), true); 417 } 418 } 419 420 /** 421 * Returns an input stream for reading the contents of the specified 422 * zip file entry. 423 * @param ze the zip file entry 424 * @return an input stream for reading the contents of the specified 425 * zip file entry 426 * @throws ZipException if a zip file format error has occurred 427 * @throws IOException if an I/O error has occurred 428 * @throws SecurityException if any of the jar file entries 429 * are incorrectly signed. 430 * @throws IllegalStateException 431 * may be thrown if the jar file has been closed 432 */ getInputStream(ZipEntry ze)433 public synchronized InputStream getInputStream(ZipEntry ze) 434 throws IOException 435 { 436 maybeInstantiateVerifier(); 437 if (jv == null) { 438 return super.getInputStream(ze); 439 } 440 if (!jvInitialized) { 441 initializeVerifier(); 442 jvInitialized = true; 443 // could be set to null after a call to 444 // initializeVerifier if we have nothing to 445 // verify 446 if (jv == null) 447 return super.getInputStream(ze); 448 } 449 450 // wrap a verifier stream around the real stream 451 return new JarVerifier.VerifierStream( 452 getManifestFromReference(), 453 ze instanceof JarFileEntry ? 454 (JarEntry) ze : getJarEntry(ze.getName()), 455 super.getInputStream(ze), 456 jv); 457 } 458 459 // Statics for hand-coded Boyer-Moore search 460 private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'}; 461 // The bad character shift for "class-path" 462 private static final int[] CLASSPATH_LASTOCC; 463 // The good suffix shift for "class-path" 464 private static final int[] CLASSPATH_OPTOSFT; 465 466 static { 467 CLASSPATH_LASTOCC = new int[128]; 468 CLASSPATH_OPTOSFT = new int[10]; 469 CLASSPATH_LASTOCC[(int)'c'] = 1; 470 CLASSPATH_LASTOCC[(int)'l'] = 2; 471 CLASSPATH_LASTOCC[(int)'s'] = 5; 472 CLASSPATH_LASTOCC[(int)'-'] = 6; 473 CLASSPATH_LASTOCC[(int)'p'] = 7; 474 CLASSPATH_LASTOCC[(int)'a'] = 8; 475 CLASSPATH_LASTOCC[(int)'t'] = 9; 476 CLASSPATH_LASTOCC[(int)'h'] = 10; 477 for (int i=0; i<9; i++) 478 CLASSPATH_OPTOSFT[i] = 10; 479 CLASSPATH_OPTOSFT[9]=1; 480 } 481 getManEntry()482 private synchronized JarEntry getManEntry() { 483 if (manEntry == null) { 484 // First look up manifest entry using standard name 485 manEntry = getJarEntry(MANIFEST_NAME); 486 if (manEntry == null) { 487 // If not found, then iterate through all the "META-INF/" 488 // entries to find a match. 489 String[] names = getMetaInfEntryNames(); 490 if (names != null) { 491 for (int i = 0; i < names.length; i++) { 492 if (MANIFEST_NAME.equals( 493 names[i].toUpperCase(Locale.ENGLISH))) { 494 manEntry = getJarEntry(names[i]); 495 break; 496 } 497 } 498 } 499 } 500 } 501 return manEntry; 502 } 503 504 /** 505 * Returns {@code true} iff this JAR file has a manifest with the 506 * Class-Path attribute 507 * @hide 508 */ hasClassPathAttribute()509 public boolean hasClassPathAttribute() throws IOException { 510 checkForSpecialAttributes(); 511 return hasClassPathAttribute; 512 } 513 514 /** 515 * Returns true if the pattern {@code src} is found in {@code b}. 516 * The {@code lastOcc} and {@code optoSft} arrays are the precomputed 517 * bad character and good suffix shifts. 518 */ match(char[] src, byte[] b, int[] lastOcc, int[] optoSft)519 private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) { 520 int len = src.length; 521 int last = b.length - len; 522 int i = 0; 523 next: 524 while (i<=last) { 525 for (int j=(len-1); j>=0; j--) { 526 char c = (char) b[i+j]; 527 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; 528 if (c != src[j]) { 529 i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); 530 continue next; 531 } 532 } 533 return true; 534 } 535 return false; 536 } 537 538 /** 539 * On first invocation, check if the JAR file has the Class-Path 540 * attribute. A no-op on subsequent calls. 541 */ checkForSpecialAttributes()542 private void checkForSpecialAttributes() throws IOException { 543 if (hasCheckedSpecialAttributes) return; 544 // Android-changed: Doesn't make sense on android: 545 //if (!isKnownNotToHaveSpecialAttributes()) { 546 { 547 JarEntry manEntry = getManEntry(); 548 if (manEntry != null) { 549 byte[] b = getBytes(manEntry); 550 if (match(CLASSPATH_CHARS, b, CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT)) 551 hasClassPathAttribute = true; 552 } 553 } 554 hasCheckedSpecialAttributes = true; 555 } 556 557 558 // Android-changed: Doesn't make sense on android: 559 /*private static String javaHome; 560 private static volatile String[] jarNames; 561 private boolean isKnownNotToHaveSpecialAttributes() { 562 // Optimize away even scanning of manifest for jar files we 563 // deliver which don't have a class-path attribute. If one of 564 // these jars is changed to include such an attribute this code 565 // must be changed. 566 if (javaHome == null) { 567 javaHome = AccessController.doPrivileged( 568 new GetPropertyAction("java.home")); 569 } 570 if (jarNames == null) { 571 String[] names = new String[11]; 572 String fileSep = File.separator; 573 int i = 0; 574 names[i++] = fileSep + "rt.jar"; 575 names[i++] = fileSep + "jsse.jar"; 576 names[i++] = fileSep + "jce.jar"; 577 names[i++] = fileSep + "charsets.jar"; 578 names[i++] = fileSep + "dnsns.jar"; 579 names[i++] = fileSep + "zipfs.jar"; 580 names[i++] = fileSep + "localedata.jar"; 581 names[i++] = fileSep = "cldrdata.jar"; 582 names[i++] = fileSep + "sunjce_provider.jar"; 583 names[i++] = fileSep + "sunpkcs11.jar"; 584 names[i++] = fileSep + "sunec.jar"; 585 jarNames = names; 586 } 587 588 String name = getName(); 589 String localJavaHome = javaHome; 590 if (name.startsWith(localJavaHome)) { 591 String[] names = jarNames; 592 for (int i = 0; i < names.length; i++) { 593 if (name.endsWith(names[i])) { 594 return true; 595 } 596 } 597 } 598 return false; 599 }*/ 600 newEntry(ZipEntry ze)601 JarEntry newEntry(ZipEntry ze) { 602 return new JarFileEntry(ze); 603 } 604 } 605