1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17package org.apache.harmony.tests.java.util.jar; 18 19 20import java.io.ByteArrayOutputStream; 21import java.io.File; 22import java.io.FileOutputStream; 23import java.io.IOException; 24import java.io.InputStream; 25import java.net.URL; 26import java.security.CodeSigner; 27import java.security.InvalidKeyException; 28import java.security.InvalidParameterException; 29import java.security.Permission; 30import java.security.PrivateKey; 31import java.security.Provider; 32import java.security.PublicKey; 33import java.security.Security; 34import java.security.SignatureException; 35import java.security.SignatureSpi; 36import java.security.cert.Certificate; 37import java.security.cert.X509Certificate; 38import java.util.Arrays; 39import java.util.Enumeration; 40import java.util.List; 41import java.util.Vector; 42import java.util.concurrent.Callable; 43import java.util.concurrent.ExecutorService; 44import java.util.concurrent.Executors; 45import java.util.concurrent.Future; 46import java.util.concurrent.TimeUnit; 47import java.util.concurrent.TimeoutException; 48import java.util.jar.Attributes; 49import java.util.jar.JarEntry; 50import java.util.jar.JarFile; 51import java.util.jar.JarOutputStream; 52import java.util.jar.Manifest; 53import java.util.zip.ZipEntry; 54import java.util.zip.ZipException; 55import java.util.zip.ZipFile; 56import junit.framework.TestCase; 57import tests.support.resource.Support_Resources; 58 59 60public class JarFileTest extends TestCase { 61 62 // BEGIN android-added 63 public byte[] getAllBytesFromStream(InputStream is) throws IOException { 64 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 65 byte[] buf = new byte[666]; 66 int iRead; 67 int off; 68 while (is.available() > 0) { 69 iRead = is.read(buf, 0, buf.length); 70 if (iRead > 0) bs.write(buf, 0, iRead); 71 } 72 return bs.toByteArray(); 73 } 74 75 // END android-added 76 77 private final String jarName = "hyts_patch.jar"; // a 'normal' jar file 78 79 private final String jarName2 = "hyts_patch2.jar"; 80 81 private final String jarName3 = "hyts_manifest1.jar"; 82 83 private final String jarName4 = "hyts_signed.jar"; 84 85 private final String jarName5 = "hyts_signed_inc.jar"; 86 87 private final String jarName6 = "hyts_signed_sha256withrsa.jar"; 88 89 private final String jarName7 = "hyts_signed_sha256digest_sha256withrsa.jar"; 90 91 private final String jarName8 = "hyts_signed_sha512digest_sha512withecdsa.jar"; 92 93 private final String jarName9 = "hyts_signed_sha256digest_sha256withecdsa.jar"; 94 95 private final String authAttrsJar = "hyts_signed_authAttrs.jar"; 96 97 private final String entryName = "foo/bar/A.class"; 98 99 private final String entryName3 = "coucou/FileAccess.class"; 100 101 private final String integrateJar = "Integrate.jar"; 102 103 private final String integrateJarEntry = "Test.class"; 104 105 private final String emptyEntryJar = "EmptyEntries_signed.jar"; 106 107 /* 108 * /usr/bin/openssl genrsa 2048 > root1.pem 109 * /usr/bin/openssl req -new -key root1.pem -out root1.csr -subj '/CN=root1' 110 * /usr/bin/openssl x509 -req -days 3650 -in root1.csr -signkey root1.pem -out root1.crt 111 * /usr/bin/openssl genrsa 2048 > root2.pem 112 * /usr/bin/openssl req -new -key root2.pem -out root2.csr -subj '/CN=root2' 113 * echo 4000 > root1.srl 114 * echo 8000 > root2.srl 115 * /usr/bin/openssl x509 -req -days 3650 -in root2.csr -CA root1.crt -CAkey root1.pem -out root2.crt 116 * /usr/bin/openssl x509 -req -days 3650 -in root1.csr -CA root2.crt -CAkey root2.pem -out root1.crt 117 * /usr/bin/openssl genrsa 2048 > signer.pem 118 * /usr/bin/openssl req -new -key signer.pem -out signer.csr -subj '/CN=signer' 119 * /usr/bin/openssl x509 -req -days 3650 -in signer.csr -CA root1.crt -CAkey root1.pem -out signer.crt 120 * /usr/bin/openssl pkcs12 -inkey signer.pem -in signer.crt -export -out signer.p12 -name signer -passout pass:certloop 121 * keytool -importkeystore -srckeystore signer.p12 -srcstoretype PKCS12 -destkeystore signer.jks -srcstorepass certloop -deststorepass certloop 122 * cat signer.crt root1.crt root2.crt > chain.crt 123 * zip -d hyts_certLoop.jar 'META-INF/*' 124 * jarsigner -keystore signer.jks -certchain chain.crt -storepass certloop hyts_certLoop.jar signer 125 */ 126 private final String certLoopJar = "hyts_certLoop.jar"; 127 128 private final String emptyEntry1 = "subfolder/internalSubset01.js"; 129 130 private final String emptyEntry2 = "svgtest.js"; 131 132 private final String emptyEntry3 = "svgunit.js"; 133 134 private static final String VALID_CHAIN_JAR = "hyts_signed_validChain.jar"; 135 136 private static final String INVALID_CHAIN_JAR = "hyts_signed_invalidChain.jar"; 137 138 private static final String AMBIGUOUS_SIGNERS_JAR = "hyts_signed_ambiguousSignerArray.jar"; 139 140 private File resources; 141 142 // custom security manager 143 SecurityManager sm = new SecurityManager() { 144 final String forbidenPermissionName = "user.dir"; 145 146 public void checkPermission(Permission perm) { 147 if (perm.getName().equals(forbidenPermissionName)) { 148 throw new SecurityException(); 149 } 150 } 151 }; 152 153 @Override 154 protected void setUp() { 155 resources = Support_Resources.createTempFolder(); 156 } 157 158 /** 159 * java.util.jar.JarFile#JarFile(java.io.File) 160 */ 161 public void test_ConstructorLjava_io_File() { 162 try { 163 JarFile jarFile = new JarFile(new File("Wrong.file")); 164 fail("Should throw IOException"); 165 } catch (IOException e) { 166 // expected 167 } 168 169 try { 170 Support_Resources.copyFile(resources, null, jarName); 171 JarFile jarFile = new JarFile(new File(resources, jarName)); 172 } catch (IOException e) { 173 fail("Should not throw IOException"); 174 } 175 } 176 177 /** 178 * java.util.jar.JarFile#JarFile(java.lang.String) 179 */ 180 public void test_ConstructorLjava_lang_String() { 181 try { 182 JarFile jarFile = new JarFile("Wrong.file"); 183 fail("Should throw IOException"); 184 } catch (IOException e) { 185 // expected 186 } 187 188 try { 189 Support_Resources.copyFile(resources, null, jarName); 190 String fileName = (new File(resources, jarName)).getCanonicalPath(); 191 JarFile jarFile = new JarFile(fileName); 192 } catch (IOException e) { 193 fail("Should not throw IOException"); 194 } 195 } 196 197 /** 198 * java.util.jar.JarFile#JarFile(java.lang.String, boolean) 199 */ 200 public void test_ConstructorLjava_lang_StringZ() { 201 try { 202 JarFile jarFile = new JarFile("Wrong.file", false); 203 fail("Should throw IOException"); 204 } catch (IOException e) { 205 // expected 206 } 207 208 try { 209 Support_Resources.copyFile(resources, null, jarName); 210 String fileName = (new File(resources, jarName)).getCanonicalPath(); 211 JarFile jarFile = new JarFile(fileName, true); 212 } catch (IOException e) { 213 fail("Should not throw IOException"); 214 } 215 } 216 217 /** 218 * java.util.jar.JarFile#JarFile(java.io.File, boolean) 219 */ 220 public void test_ConstructorLjava_io_FileZ() { 221 try { 222 JarFile jarFile = new JarFile(new File("Wrong.file"), true); 223 fail("Should throw IOException"); 224 } catch (IOException e) { 225 // expected 226 } 227 228 try { 229 Support_Resources.copyFile(resources, null, jarName); 230 JarFile jarFile = new JarFile(new File(resources, jarName), false); 231 } catch (IOException e) { 232 fail("Should not throw IOException"); 233 } 234 } 235 236 /** 237 * java.util.jar.JarFile#JarFile(java.io.File, boolean, int) 238 */ 239 public void test_ConstructorLjava_io_FileZI() { 240 try { 241 JarFile jarFile = new JarFile(new File("Wrong.file"), true, 242 ZipFile.OPEN_READ); 243 fail("Should throw IOException"); 244 } catch (IOException e) { 245 // expected 246 } 247 248 try { 249 Support_Resources.copyFile(resources, null, jarName); 250 JarFile jarFile = new JarFile(new File(resources, jarName), false, 251 ZipFile.OPEN_READ); 252 } catch (IOException e) { 253 fail("Should not throw IOException"); 254 } 255 256 try { 257 Support_Resources.copyFile(resources, null, jarName); 258 JarFile jarFile = new JarFile(new File(resources, jarName), false, 259 ZipFile.OPEN_READ | ZipFile.OPEN_DELETE + 33); 260 fail("Should throw IllegalArgumentException"); 261 } catch (IOException e) { 262 fail("Should not throw IOException"); 263 } catch (IllegalArgumentException e) { 264 // expected 265 } 266 } 267 268 /** 269 * Constructs JarFile object. 270 * 271 * java.util.jar.JarFile#JarFile(java.io.File) 272 * java.util.jar.JarFile#JarFile(java.lang.String) 273 */ 274 public void testConstructor_file() throws IOException { 275 File f = new File(resources, jarName); 276 Support_Resources.copyFile(resources, null, jarName); 277 assertTrue(new JarFile(f).getEntry(entryName).getName().equals( 278 entryName)); 279 assertTrue(new JarFile(f.getPath()).getEntry(entryName).getName() 280 .equals(entryName)); 281 } 282 283 /** 284 * java.util.jar.JarFile#entries() 285 */ 286 public void test_entries() throws Exception { 287 /* 288 * Note only (and all of) the following should be contained in the file 289 * META-INF/ META-INF/MANIFEST.MF foo/ foo/bar/ foo/bar/A.class Blah.txt 290 */ 291 Support_Resources.copyFile(resources, null, jarName); 292 JarFile jarFile = new JarFile(new File(resources, jarName)); 293 Enumeration<JarEntry> e = jarFile.entries(); 294 int i; 295 for (i = 0; e.hasMoreElements(); i++) { 296 e.nextElement(); 297 } 298 assertEquals(jarFile.size(), i); 299 jarFile.close(); 300 assertEquals(6, i); 301 } 302 303 public void test_entries2() throws Exception { 304 Support_Resources.copyFile(resources, null, jarName); 305 JarFile jarFile = new JarFile(new File(resources, jarName)); 306 Enumeration<JarEntry> enumeration = jarFile.entries(); 307 jarFile.close(); 308 try { 309 enumeration.hasMoreElements(); 310 fail("hasMoreElements() did not detect a closed jar file"); 311 } catch (IllegalStateException e) { 312 } 313 Support_Resources.copyFile(resources, null, jarName); 314 jarFile = new JarFile(new File(resources, jarName)); 315 enumeration = jarFile.entries(); 316 jarFile.close(); 317 try { 318 enumeration.nextElement(); 319 fail("nextElement() did not detect closed jar file"); 320 } catch (IllegalStateException e) { 321 } 322 } 323 324 /** 325 * @throws IOException 326 * java.util.jar.JarFile#getJarEntry(java.lang.String) 327 */ 328 public void test_getEntryLjava_lang_String() throws IOException { 329 try { 330 Support_Resources.copyFile(resources, null, jarName); 331 JarFile jarFile = new JarFile(new File(resources, jarName)); 332 assertEquals("Error in returned entry", 311, jarFile.getEntry( 333 entryName).getSize()); 334 jarFile.close(); 335 } catch (Exception e) { 336 fail("Exception during test: " + e.toString()); 337 } 338 339 Support_Resources.copyFile(resources, null, jarName); 340 JarFile jarFile = new JarFile(new File(resources, jarName)); 341 Enumeration<JarEntry> enumeration = jarFile.entries(); 342 assertTrue(enumeration.hasMoreElements()); 343 while (enumeration.hasMoreElements()) { 344 JarEntry je = enumeration.nextElement(); 345 jarFile.getEntry(je.getName()); 346 } 347 348 enumeration = jarFile.entries(); 349 assertTrue(enumeration.hasMoreElements()); 350 JarEntry je = enumeration.nextElement(); 351 try { 352 jarFile.close(); 353 jarFile.getEntry(je.getName()); 354 // fail("IllegalStateException expected."); 355 } catch (IllegalStateException ee) { // Per documentation exception 356 // may be thrown. 357 // expected 358 } 359 } 360 361 /** 362 * @throws IOException 363 * java.util.jar.JarFile#getJarEntry(java.lang.String) 364 */ 365 public void test_getJarEntryLjava_lang_String() throws IOException { 366 try { 367 Support_Resources.copyFile(resources, null, jarName); 368 JarFile jarFile = new JarFile(new File(resources, jarName)); 369 assertEquals("Error in returned entry", 311, jarFile.getJarEntry( 370 entryName).getSize()); 371 jarFile.close(); 372 } catch (Exception e) { 373 fail("Exception during test: " + e.toString()); 374 } 375 376 Support_Resources.copyFile(resources, null, jarName); 377 JarFile jarFile = new JarFile(new File(resources, jarName)); 378 Enumeration<JarEntry> enumeration = jarFile.entries(); 379 assertTrue(enumeration.hasMoreElements()); 380 while (enumeration.hasMoreElements()) { 381 JarEntry je = enumeration.nextElement(); 382 jarFile.getJarEntry(je.getName()); 383 } 384 385 enumeration = jarFile.entries(); 386 assertTrue(enumeration.hasMoreElements()); 387 JarEntry je = enumeration.nextElement(); 388 try { 389 jarFile.close(); 390 jarFile.getJarEntry(je.getName()); 391 // fail("IllegalStateException expected."); 392 } catch (IllegalStateException ee) { // Per documentation exception 393 // may be thrown. 394 // expected 395 } 396 } 397 398 399 /** 400 * java.util.jar.JarFile#getJarEntry(java.lang.String) 401 */ 402 public void testGetJarEntry() throws Exception { 403 Support_Resources.copyFile(resources, null, jarName); 404 JarFile jarFile = new JarFile(new File(resources, jarName)); 405 assertEquals("Error in returned entry", 311, jarFile.getEntry( 406 entryName).getSize()); 407 jarFile.close(); 408 409 // tests for signed jars 410 // test all signed jars in the /Testres/Internal/SignedJars directory 411 String jarDirUrl = Support_Resources 412 .getResourceURL("/../internalres/signedjars"); 413 Vector<String> signedJars = new Vector<String>(); 414 try { 415 InputStream is = new URL(jarDirUrl + "/jarlist.txt").openStream(); 416 while (is.available() > 0) { 417 StringBuilder linebuff = new StringBuilder(80); // Typical line 418 // length 419 done: while (true) { 420 int nextByte = is.read(); 421 switch (nextByte) { 422 case -1: 423 break done; 424 case (byte) '\r': 425 if (linebuff.length() == 0) { 426 // ignore 427 } 428 break done; 429 case (byte) '\n': 430 if (linebuff.length() == 0) { 431 // ignore 432 } 433 break done; 434 default: 435 linebuff.append((char) nextByte); 436 } 437 } 438 if (linebuff.length() == 0) { 439 break; 440 } 441 String line = linebuff.toString(); 442 signedJars.add(line); 443 } 444 is.close(); 445 } catch (IOException e) { 446 // no list of jars found 447 } 448 449 for (int i = 0; i < signedJars.size(); i++) { 450 String jarName = signedJars.get(i); 451 try { 452 File file = Support_Resources.getExternalLocalFile(jarDirUrl 453 + "/" + jarName); 454 jarFile = new JarFile(file, true); 455 boolean foundCerts = false; 456 Enumeration<JarEntry> e = jarFile.entries(); 457 while (e.hasMoreElements()) { 458 JarEntry entry = e.nextElement(); 459 InputStream is = jarFile.getInputStream(entry); 460 is.skip(100000); 461 is.close(); 462 Certificate[] certs = entry.getCertificates(); 463 if (certs != null && certs.length > 0) { 464 foundCerts = true; 465 break; 466 } 467 } 468 assertTrue( 469 "No certificates found during signed jar test for jar \"" 470 + jarName + "\"", foundCerts); 471 } catch (IOException e) { 472 fail("Exception during signed jar test for jar \"" + jarName 473 + "\": " + e.toString()); 474 } 475 } 476 } 477 478 /** 479 * java.util.jar.JarFile#getManifest() 480 */ 481 public void test_getManifest() { 482 // Test for method java.util.jar.Manifest 483 // java.util.jar.JarFile.getManifest() 484 try { 485 Support_Resources.copyFile(resources, null, jarName); 486 JarFile jarFile = new JarFile(new File(resources, jarName)); 487 assertNotNull("Error--Manifest not returned", jarFile.getManifest()); 488 jarFile.close(); 489 } catch (Exception e) { 490 fail("Exception during 1st test: " + e.toString()); 491 } 492 try { 493 Support_Resources.copyFile(resources, null, jarName2); 494 JarFile jarFile = new JarFile(new File(resources, jarName2)); 495 assertNull("Error--should have returned null", jarFile 496 .getManifest()); 497 jarFile.close(); 498 } catch (Exception e) { 499 fail("Exception during 2nd test: " + e.toString()); 500 } 501 502 try { 503 // jarName3 was created using the following test 504 Support_Resources.copyFile(resources, null, jarName3); 505 JarFile jarFile = new JarFile(new File(resources, jarName3)); 506 assertNotNull("Should find manifest without verifying", jarFile 507 .getManifest()); 508 jarFile.close(); 509 } catch (Exception e) { 510 fail("Exception during 3rd test: " + e.toString()); 511 } 512 513 try { 514 // this is used to create jarName3 used in the previous test 515 Manifest manifest = new Manifest(); 516 Attributes attributes = manifest.getMainAttributes(); 517 attributes.put(new Attributes.Name("Manifest-Version"), "1.0"); 518 ByteArrayOutputStream manOut = new ByteArrayOutputStream(); 519 manifest.write(manOut); 520 byte[] manBytes = manOut.toByteArray(); 521 File file = File.createTempFile("hyts_manifest1", ".jar"); 522 JarOutputStream jarOut = new JarOutputStream(new FileOutputStream( 523 file.getAbsolutePath())); 524 ZipEntry entry = new ZipEntry("META-INF/"); 525 entry.setSize(0); 526 jarOut.putNextEntry(entry); 527 entry = new ZipEntry(JarFile.MANIFEST_NAME); 528 entry.setSize(manBytes.length); 529 jarOut.putNextEntry(entry); 530 jarOut.write(manBytes); 531 entry = new ZipEntry("myfile"); 532 entry.setSize(1); 533 jarOut.putNextEntry(entry); 534 jarOut.write(65); 535 jarOut.close(); 536 JarFile jar = new JarFile(file.getAbsolutePath(), false); 537 assertNotNull("Should find manifest without verifying", jar 538 .getManifest()); 539 jar.close(); 540 file.delete(); 541 } catch (IOException e) { 542 fail("IOException 3"); 543 } 544 try { 545 Support_Resources.copyFile(resources, null, jarName2); 546 JarFile jF = new JarFile(new File(resources, jarName2)); 547 jF.close(); 548 jF.getManifest(); 549 fail("FAILED: expected IllegalStateException"); 550 } catch (IllegalStateException ise) { 551 // expected; 552 } catch (Exception e) { 553 fail("Exception during 4th test: " + e.toString()); 554 } 555 556 Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); 557 JarFile jf; 558 try { 559 jf = new JarFile(new File(resources, "Broken_manifest.jar")); 560 jf.getManifest(); 561 fail("IOException expected."); 562 } catch (IOException e) { 563 // expected. 564 } 565 } 566 567 /** 568 * java.util.jar.JarFile#getInputStream(java.util.zip.ZipEntry) 569 */ 570 // This test doesn't pass on RI. If entry size is set up incorrectly, 571 // SecurityException is thrown. But SecurityException is thrown on RI only 572 // if jar file is signed incorrectly. 573 public void test_getInputStreamLjava_util_jar_JarEntry_subtest0() throws Exception { 574 File signedFile = null; 575 try { 576 Support_Resources.copyFile(resources, null, jarName4); 577 signedFile = new File(resources, jarName4); 578 } catch (Exception e) { 579 fail("Failed to create local file 2: " + e); 580 } 581 582 try { 583 JarFile jar = new JarFile(signedFile); 584 JarEntry entry = new JarEntry(entryName3); 585 InputStream in = jar.getInputStream(entry); 586 in.read(); 587 } catch (Exception e) { 588 fail("Exception during test 3: " + e); 589 } 590 591 try { 592 JarFile jar = new JarFile(signedFile); 593 JarEntry entry = new JarEntry(entryName3); 594 InputStream in = jar.getInputStream(entry); 595 // BEGIN android-added 596 byte[] dummy = getAllBytesFromStream(in); 597 // END android-added 598 assertNull("found certificates", entry.getCertificates()); 599 } catch (Exception e) { 600 fail("Exception during test 4: " + e); 601 } 602 603 try { 604 JarFile jar = new JarFile(signedFile); 605 JarEntry entry = new JarEntry(entryName3); 606 entry.setSize(1076); 607 InputStream in = jar.getInputStream(entry); 608 // BEGIN android-added 609 byte[] dummy = getAllBytesFromStream(in); 610 // END android-added 611 fail("SecurityException should be thrown."); 612 } catch (SecurityException e) { 613 // expected 614 } catch (Exception e) { 615 fail("Exception during test 5: " + e); 616 } 617 618 try { 619 Support_Resources.copyFile(resources, null, jarName5); 620 signedFile = new File(resources, jarName5); 621 } catch (Exception e) { 622 fail("Failed to create local file 5: " + e); 623 } 624 625 try { 626 JarFile jar = new JarFile(signedFile); 627 JarEntry entry = new JarEntry(entryName3); 628 InputStream in = jar.getInputStream(entry); 629 fail("SecurityException should be thrown."); 630 } catch (SecurityException e) { 631 // expected 632 } catch (Exception e) { 633 fail("Exception during test 5: " + e); 634 } 635 636 // SHA1 digest, SHA256withRSA signed JAR 637 checkSignedJar(jarName6); 638 639 // SHA-256 digest, SHA256withRSA signed JAR 640 checkSignedJar(jarName7); 641 642 // SHA-512 digest, SHA512withECDSA signed JAR 643 checkSignedJar(jarName8); 644 645 // JAR with a signature that has PKCS#7 Authenticated Attributes 646 checkSignedJar(authAttrsJar); 647 648 // JAR with certificates that loop 649 checkSignedJar(certLoopJar, 3); 650 } 651 652 /** 653 * This test uses a jar file signed with an algorithm that has its own OID 654 * that is valid as a signature type. SHA256withECDSA is an algorithm that 655 * isn't combined as DigestAlgorithm + "with" + DigestEncryptionAlgorithm 656 * like RSAEncryption needs to be. 657 */ 658 public void testJarFile_Signed_Valid_DigestEncryptionAlgorithm() throws Exception { 659 checkSignedJar(jarName9); 660 } 661 662 /** 663 * Checks that a JAR is signed correctly with a signature length of 1. 664 */ 665 private void checkSignedJar(String jarName) throws Exception { 666 checkSignedJar(jarName, 1); 667 } 668 669 /** 670 * Checks that a JAR is signed correctly with a signature length of sigLength. 671 */ 672 private void checkSignedJar(String jarName, final int sigLength) throws Exception { 673 Support_Resources.copyFile(resources, null, jarName); 674 675 final File file = new File(resources, jarName); 676 677 ExecutorService executor = Executors.newSingleThreadExecutor(); 678 Future<Boolean> future = executor.submit(new Callable<Boolean>() { 679 @Override 680 public Boolean call() throws Exception { 681 JarFile jarFile = new JarFile(file, true); 682 try { 683 Enumeration<JarEntry> e = jarFile.entries(); 684 while (e.hasMoreElements()) { 685 JarEntry entry = e.nextElement(); 686 InputStream is = jarFile.getInputStream(entry); 687 is.skip(100000); 688 is.close(); 689 Certificate[] certs = entry.getCertificates(); 690 if (certs != null && certs.length > 0) { 691 assertEquals(sigLength, certs.length); 692 return true; 693 } 694 } 695 return false; 696 } finally { 697 jarFile.close(); 698 } 699 } 700 }); 701 executor.shutdown(); 702 final boolean foundCerts; 703 try { 704 foundCerts = future.get(10, TimeUnit.SECONDS); 705 } catch (TimeoutException e) { 706 fail("Could not finish building chain; possibly confused by loops"); 707 return; // Not actually reached. 708 } 709 710 assertTrue( 711 "No certificates found during signed jar test for jar \"" 712 + jarName + "\"", foundCerts); 713 } 714 715 private static class Results { 716 public Certificate[] certificates; 717 public CodeSigner[] signers; 718 } 719 720 private Results getSignedJarCerts(String jarName) throws Exception { 721 Support_Resources.copyFile(resources, null, jarName); 722 723 File file = new File(resources, jarName); 724 Results results = new Results(); 725 726 JarFile jarFile = new JarFile(file, true, ZipFile.OPEN_READ); 727 try { 728 729 Enumeration<JarEntry> e = jarFile.entries(); 730 while (e.hasMoreElements()) { 731 JarEntry entry = e.nextElement(); 732 InputStream is = jarFile.getInputStream(entry); 733 // Skip bytes because we have to read the entire file for it to read signatures. 734 is.skip(entry.getSize()); 735 is.close(); 736 Certificate[] certs = entry.getCertificates(); 737 CodeSigner[] signers = entry.getCodeSigners(); 738 if (certs != null && certs.length > 0) { 739 results.certificates = certs; 740 results.signers = signers; 741 break; 742 } 743 } 744 } finally { 745 jarFile.close(); 746 } 747 748 return results; 749 } 750 751 public void testJarFile_Signed_ValidChain() throws Exception { 752 Results result = getSignedJarCerts(VALID_CHAIN_JAR); 753 assertNotNull(result); 754 assertEquals(Arrays.deepToString(result.certificates), 3, result.certificates.length); 755 assertEquals(Arrays.deepToString(result.signers), 1, result.signers.length); 756 assertEquals(3, result.signers[0].getSignerCertPath().getCertificates().size()); 757 assertEquals("CN=fake-chain", ((X509Certificate) result.certificates[0]).getSubjectDN().toString()); 758 assertEquals("CN=intermediate1", ((X509Certificate) result.certificates[1]).getSubjectDN().toString()); 759 assertEquals("CN=root1", ((X509Certificate) result.certificates[2]).getSubjectDN().toString()); 760 } 761 762 public void testJarFile_Signed_InvalidChain() throws Exception { 763 Results result = getSignedJarCerts(INVALID_CHAIN_JAR); 764 assertNotNull(result); 765 assertEquals(Arrays.deepToString(result.certificates), 3, result.certificates.length); 766 assertEquals(Arrays.deepToString(result.signers), 1, result.signers.length); 767 assertEquals(3, result.signers[0].getSignerCertPath().getCertificates().size()); 768 assertEquals("CN=fake-chain", ((X509Certificate) result.certificates[0]).getSubjectDN().toString()); 769 assertEquals("CN=intermediate1", ((X509Certificate) result.certificates[1]).getSubjectDN().toString()); 770 assertEquals("CN=root1", ((X509Certificate) result.certificates[2]).getSubjectDN().toString()); 771 } 772 773 public void testJarFile_Signed_AmbiguousSigners() throws Exception { 774 Results result = getSignedJarCerts(AMBIGUOUS_SIGNERS_JAR); 775 assertNotNull(result); 776 assertEquals(Arrays.deepToString(result.certificates), 2, result.certificates.length); 777 assertEquals(Arrays.deepToString(result.signers), 2, result.signers.length); 778 assertEquals(1, result.signers[0].getSignerCertPath().getCertificates().size()); 779 assertEquals(1, result.signers[1].getSignerCertPath().getCertificates().size()); 780 } 781 782 /* 783 * The jar created by 1.4 which does not provide a 784 * algorithm-Digest-Manifest-Main-Attributes entry in .SF file. 785 */ 786 public void test_Jar_created_before_java_5() throws IOException { 787 String modifiedJarName = "Created_by_1_4.jar"; 788 Support_Resources.copyFile(resources, null, modifiedJarName); 789 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 790 true); 791 Enumeration<JarEntry> entries = jarFile.entries(); 792 while (entries.hasMoreElements()) { 793 ZipEntry zipEntry = entries.nextElement(); 794 jarFile.getInputStream(zipEntry); 795 } 796 } 797 798 /* The jar is intact, then everything is all right. */ 799 public void test_JarFile_Integrate_Jar() throws IOException { 800 String modifiedJarName = "Integrate.jar"; 801 Support_Resources.copyFile(resources, null, modifiedJarName); 802 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 803 true); 804 Enumeration<JarEntry> entries = jarFile.entries(); 805 while (entries.hasMoreElements()) { 806 ZipEntry zipEntry = entries.nextElement(); 807 jarFile.getInputStream(zipEntry).skip(Long.MAX_VALUE); 808 } 809 } 810 811 /** 812 * The jar is intact, but the entry object is modified. 813 */ 814 public void testJarVerificationModifiedEntry() throws IOException { 815 Support_Resources.copyFile(resources, null, integrateJar); 816 File f = new File(resources, integrateJar); 817 818 JarFile jarFile = new JarFile(f); 819 ZipEntry zipEntry = jarFile.getJarEntry(integrateJarEntry); 820 zipEntry.setSize(zipEntry.getSize() + 1); 821 jarFile.getInputStream(zipEntry).skip(Long.MAX_VALUE); 822 823 jarFile = new JarFile(f); 824 zipEntry = jarFile.getJarEntry(integrateJarEntry); 825 zipEntry.setSize(zipEntry.getSize() - 1); 826 try { 827 //jarFile.getInputStream(zipEntry).skip(Long.MAX_VALUE); 828 jarFile.getInputStream(zipEntry).read(new byte[5000], 0, 5000); 829 fail("SecurityException expected"); 830 } catch (SecurityException e) { 831 // desired 832 } 833 } 834 835 /* 836 * If another entry is inserted into Manifest, no security exception will be 837 * thrown out. 838 */ 839 public void test_JarFile_InsertEntry_in_Manifest_Jar() throws IOException { 840 String modifiedJarName = "Inserted_Entry_Manifest.jar"; 841 Support_Resources.copyFile(resources, null, modifiedJarName); 842 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 843 true); 844 Enumeration<JarEntry> entries = jarFile.entries(); 845 int count = 0; 846 while (entries.hasMoreElements()) { 847 848 ZipEntry zipEntry = entries.nextElement(); 849 jarFile.getInputStream(zipEntry); 850 count++; 851 } 852 assertEquals(5, count); 853 } 854 855 /* 856 * If another entry is inserted into Manifest, no security exception will be 857 * thrown out. 858 */ 859 public void test_Inserted_Entry_Manifest_with_DigestCode() 860 throws IOException { 861 String modifiedJarName = "Inserted_Entry_Manifest_with_DigestCode.jar"; 862 Support_Resources.copyFile(resources, null, modifiedJarName); 863 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 864 true); 865 Enumeration<JarEntry> entries = jarFile.entries(); 866 int count = 0; 867 while (entries.hasMoreElements()) { 868 ZipEntry zipEntry = entries.nextElement(); 869 jarFile.getInputStream(zipEntry); 870 count++; 871 } 872 assertEquals(5, count); 873 } 874 875 /* 876 * The content of Test.class is modified, jarFile.getInputStream will not 877 * throw security Exception, but it will anytime before the inputStream got 878 * from getInputStream method has been read to end. 879 */ 880 public void test_JarFile_Modified_Class() throws IOException { 881 String modifiedJarName = "Modified_Class.jar"; 882 Support_Resources.copyFile(resources, null, modifiedJarName); 883 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 884 true); 885 Enumeration<JarEntry> entries = jarFile.entries(); 886 while (entries.hasMoreElements()) { 887 ZipEntry zipEntry = entries.nextElement(); 888 jarFile.getInputStream(zipEntry); 889 } 890 /* The content of Test.class has been tampered. */ 891 ZipEntry zipEntry = jarFile.getEntry("Test.class"); 892 InputStream in = jarFile.getInputStream(zipEntry); 893 byte[] buffer = new byte[1024]; 894 try { 895 while (in.available() > 0) { 896 in.read(buffer); 897 } 898 fail("SecurityException expected"); 899 } catch (SecurityException e) { 900 // desired 901 } 902 } 903 904 /* 905 * In the Modified.jar, the main attributes of META-INF/MANIFEST.MF is 906 * tampered manually. Hence the RI 5.0 JarFile.getInputStream of any 907 * JarEntry will throw security exception. 908 */ 909 public void test_JarFile_Modified_Manifest_MainAttributes() 910 throws IOException { 911 String modifiedJarName = "Modified_Manifest_MainAttributes.jar"; 912 Support_Resources.copyFile(resources, null, modifiedJarName); 913 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 914 true); 915 Enumeration<JarEntry> entries = jarFile.entries(); 916 while (entries.hasMoreElements()) { 917 ZipEntry zipEntry = entries.nextElement(); 918 try { 919 jarFile.getInputStream(zipEntry); 920 fail("SecurityException expected"); 921 } catch (SecurityException e) { 922 // desired 923 } 924 } 925 } 926 927 /* 928 * It is all right in our original JarFile. If the Entry Attributes, for 929 * example Test.class in our jar, the jarFile.getInputStream will throw 930 * Security Exception. 931 */ 932 public void test_JarFile_Modified_Manifest_EntryAttributes() 933 throws IOException { 934 String modifiedJarName = "Modified_Manifest_EntryAttributes.jar"; 935 Support_Resources.copyFile(resources, null, modifiedJarName); 936 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 937 true); 938 Enumeration<JarEntry> entries = jarFile.entries(); 939 while (entries.hasMoreElements()) { 940 ZipEntry zipEntry = entries.nextElement(); 941 try { 942 jarFile.getInputStream(zipEntry); 943 fail("should throw Security Exception"); 944 } catch (SecurityException e) { 945 // desired 946 } 947 } 948 } 949 950 /* 951 * If the content of the .SA file is modified, no matter what it resides, 952 * JarFile.getInputStream of any JarEntry will throw Security Exception. 953 */ 954 public void test_JarFile_Modified_SF_EntryAttributes() throws IOException { 955 String modifiedJarName = "Modified_SF_EntryAttributes.jar"; 956 Support_Resources.copyFile(resources, null, modifiedJarName); 957 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 958 true); 959 Enumeration<JarEntry> entries = jarFile.entries(); 960 while (entries.hasMoreElements()) { 961 ZipEntry zipEntry = entries.nextElement(); 962 try { 963 jarFile.getInputStream(zipEntry); 964 fail("should throw Security Exception"); 965 } catch (SecurityException e) { 966 // desired 967 } 968 } 969 } 970 971 public void test_close() throws IOException { 972 String modifiedJarName = "Modified_SF_EntryAttributes.jar"; 973 Support_Resources.copyFile(resources, null, modifiedJarName); 974 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 975 true); 976 Enumeration<JarEntry> entries = jarFile.entries(); 977 978 jarFile.close(); 979 jarFile.close(); 980 981 // Can not check IOException 982 } 983 984 /** 985 * @throws IOException 986 * java.util.jar.JarFile#getInputStream(java.util.zip.ZipEntry) 987 */ 988 public void test_getInputStreamLjava_util_jar_JarEntry() throws IOException { 989 File localFile = null; 990 try { 991 Support_Resources.copyFile(resources, null, jarName); 992 localFile = new File(resources, jarName); 993 } catch (Exception e) { 994 fail("Failed to create local file: " + e); 995 } 996 997 byte[] b = new byte[1024]; 998 try { 999 JarFile jf = new JarFile(localFile); 1000 java.io.InputStream is = jf.getInputStream(jf.getEntry(entryName)); 1001 // BEGIN android-removed 1002 // jf.close(); 1003 // END android-removed 1004 assertTrue("Returned invalid stream", is.available() > 0); 1005 int r = is.read(b, 0, 1024); 1006 is.close(); 1007 StringBuffer sb = new StringBuffer(r); 1008 for (int i = 0; i < r; i++) { 1009 sb.append((char) (b[i] & 0xff)); 1010 } 1011 String contents = sb.toString(); 1012 assertTrue("Incorrect stream read", contents.indexOf("bar") > 0); 1013 // BEGIN android-added 1014 jf.close(); 1015 // END android-added 1016 } catch (Exception e) { 1017 fail("Exception during test: " + e.toString()); 1018 } 1019 1020 try { 1021 JarFile jf = new JarFile(localFile); 1022 InputStream in = jf.getInputStream(new JarEntry("invalid")); 1023 assertNull("Got stream for non-existent entry", in); 1024 } catch (Exception e) { 1025 fail("Exception during test 2: " + e); 1026 } 1027 1028 try { 1029 Support_Resources.copyFile(resources, null, jarName); 1030 File signedFile = new File(resources, jarName); 1031 JarFile jf = new JarFile(signedFile); 1032 JarEntry jre = new JarEntry("foo/bar/A.class"); 1033 jf.getInputStream(jre); 1034 // InputStream returned in any way, exception can be thrown in case 1035 // of reading from this stream only. 1036 // fail("Should throw ZipException"); 1037 } catch (ZipException ee) { 1038 // expected 1039 } 1040 1041 try { 1042 Support_Resources.copyFile(resources, null, jarName); 1043 File signedFile = new File(resources, jarName); 1044 JarFile jf = new JarFile(signedFile); 1045 JarEntry jre = new JarEntry("foo/bar/A.class"); 1046 jf.close(); 1047 jf.getInputStream(jre); 1048 // InputStream returned in any way, exception can be thrown in case 1049 // of reading from this stream only. 1050 // The same for IOException 1051 fail("Should throw IllegalStateException"); 1052 } catch (IllegalStateException ee) { 1053 // expected 1054 } 1055 } 1056 1057 /** 1058 * The jar is intact, but the entry object is modified. 1059 */ 1060 // Regression test for issue introduced by HARMONY-4569: signed archives containing files with size 0 could not get verified. 1061 public void testJarVerificationEmptyEntry() throws IOException { 1062 Support_Resources.copyFile(resources, null, emptyEntryJar); 1063 File f = new File(resources, emptyEntryJar); 1064 1065 JarFile jarFile = new JarFile(f); 1066 1067 ZipEntry zipEntry = jarFile.getJarEntry(emptyEntry1); 1068 int res = jarFile.getInputStream(zipEntry).read(new byte[100], 0, 100); 1069 assertEquals("Wrong length of empty jar entry", -1, res); 1070 1071 zipEntry = jarFile.getJarEntry(emptyEntry2); 1072 res = jarFile.getInputStream(zipEntry).read(new byte[100], 0, 100); 1073 assertEquals("Wrong length of empty jar entry", -1, res); 1074 1075 zipEntry = jarFile.getJarEntry(emptyEntry3); 1076 res = jarFile.getInputStream(zipEntry).read(); 1077 assertEquals("Wrong length of empty jar entry", -1, res); 1078 } 1079 1080 public void testJarFile_BadSignatureProvider_Success() throws Exception { 1081 Security.insertProviderAt(new JarFileBadProvider(), 1); 1082 try { 1083 // Needs a JAR with "RSA" as digest encryption algorithm 1084 checkSignedJar(jarName6); 1085 } finally { 1086 Security.removeProvider(JarFileBadProvider.NAME); 1087 } 1088 } 1089 1090 public static class JarFileBadProvider extends Provider { 1091 public static final String NAME = "JarFileBadProvider"; 1092 1093 public JarFileBadProvider() { 1094 super(NAME, 1.0, "Bad provider for JarFileTest"); 1095 1096 put("Signature.RSA", NotReallyASignature.class.getName()); 1097 } 1098 1099 /** 1100 * This should never be instantiated, so everything throws an exception. 1101 */ 1102 public static class NotReallyASignature extends SignatureSpi { 1103 @Override 1104 protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { 1105 fail("Should not call this provider"); 1106 } 1107 1108 @Override 1109 protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { 1110 fail("Should not call this provider"); 1111 } 1112 1113 @Override 1114 protected void engineUpdate(byte b) throws SignatureException { 1115 fail("Should not call this provider"); 1116 } 1117 1118 @Override 1119 protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { 1120 fail("Should not call this provider"); 1121 } 1122 1123 @Override 1124 protected byte[] engineSign() throws SignatureException { 1125 fail("Should not call this provider"); 1126 return null; 1127 } 1128 1129 @Override 1130 protected boolean engineVerify(byte[] sigBytes) throws SignatureException { 1131 fail("Should not call this provider"); 1132 return false; 1133 } 1134 1135 @Override 1136 protected void engineSetParameter(String param, Object value) 1137 throws InvalidParameterException { 1138 fail("Should not call this provider"); 1139 } 1140 1141 @Override 1142 protected Object engineGetParameter(String param) throws InvalidParameterException { 1143 fail("Should not call this provider"); 1144 return null; 1145 } 1146 } 1147 } 1148} 1149