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 /** 304 * @throws IOException 305 * java.util.jar.JarFile#getJarEntry(java.lang.String) 306 */ 307 public void test_getEntryLjava_lang_String() throws IOException { 308 try { 309 Support_Resources.copyFile(resources, null, jarName); 310 JarFile jarFile = new JarFile(new File(resources, jarName)); 311 assertEquals("Error in returned entry", 311, jarFile.getEntry( 312 entryName).getSize()); 313 jarFile.close(); 314 } catch (Exception e) { 315 fail("Exception during test: " + e.toString()); 316 } 317 318 Support_Resources.copyFile(resources, null, jarName); 319 JarFile jarFile = new JarFile(new File(resources, jarName)); 320 Enumeration<JarEntry> enumeration = jarFile.entries(); 321 assertTrue(enumeration.hasMoreElements()); 322 while (enumeration.hasMoreElements()) { 323 JarEntry je = enumeration.nextElement(); 324 jarFile.getEntry(je.getName()); 325 } 326 327 enumeration = jarFile.entries(); 328 assertTrue(enumeration.hasMoreElements()); 329 JarEntry je = enumeration.nextElement(); 330 try { 331 jarFile.close(); 332 jarFile.getEntry(je.getName()); 333 // fail("IllegalStateException expected."); 334 } catch (IllegalStateException ee) { // Per documentation exception 335 // may be thrown. 336 // expected 337 } 338 } 339 340 /** 341 * @throws IOException 342 * java.util.jar.JarFile#getJarEntry(java.lang.String) 343 */ 344 public void test_getJarEntryLjava_lang_String() throws IOException { 345 try { 346 Support_Resources.copyFile(resources, null, jarName); 347 JarFile jarFile = new JarFile(new File(resources, jarName)); 348 assertEquals("Error in returned entry", 311, jarFile.getJarEntry( 349 entryName).getSize()); 350 jarFile.close(); 351 } catch (Exception e) { 352 fail("Exception during test: " + e.toString()); 353 } 354 355 Support_Resources.copyFile(resources, null, jarName); 356 JarFile jarFile = new JarFile(new File(resources, jarName)); 357 Enumeration<JarEntry> enumeration = jarFile.entries(); 358 assertTrue(enumeration.hasMoreElements()); 359 while (enumeration.hasMoreElements()) { 360 JarEntry je = enumeration.nextElement(); 361 jarFile.getJarEntry(je.getName()); 362 } 363 364 enumeration = jarFile.entries(); 365 assertTrue(enumeration.hasMoreElements()); 366 JarEntry je = enumeration.nextElement(); 367 try { 368 jarFile.close(); 369 jarFile.getJarEntry(je.getName()); 370 // fail("IllegalStateException expected."); 371 } catch (IllegalStateException ee) { // Per documentation exception 372 // may be thrown. 373 // expected 374 } 375 } 376 377 378 /** 379 * java.util.jar.JarFile#getJarEntry(java.lang.String) 380 */ 381 public void testGetJarEntry() throws Exception { 382 Support_Resources.copyFile(resources, null, jarName); 383 JarFile jarFile = new JarFile(new File(resources, jarName)); 384 assertEquals("Error in returned entry", 311, jarFile.getEntry( 385 entryName).getSize()); 386 jarFile.close(); 387 388 // tests for signed jars 389 // test all signed jars in the /Testres/Internal/SignedJars directory 390 String jarDirUrl = Support_Resources 391 .getResourceURL("/../internalres/signedjars"); 392 Vector<String> signedJars = new Vector<String>(); 393 try { 394 InputStream is = new URL(jarDirUrl + "/jarlist.txt").openStream(); 395 while (is.available() > 0) { 396 StringBuilder linebuff = new StringBuilder(80); // Typical line 397 // length 398 done: while (true) { 399 int nextByte = is.read(); 400 switch (nextByte) { 401 case -1: 402 break done; 403 case (byte) '\r': 404 if (linebuff.length() == 0) { 405 // ignore 406 } 407 break done; 408 case (byte) '\n': 409 if (linebuff.length() == 0) { 410 // ignore 411 } 412 break done; 413 default: 414 linebuff.append((char) nextByte); 415 } 416 } 417 if (linebuff.length() == 0) { 418 break; 419 } 420 String line = linebuff.toString(); 421 signedJars.add(line); 422 } 423 is.close(); 424 } catch (IOException e) { 425 // no list of jars found 426 } 427 428 for (int i = 0; i < signedJars.size(); i++) { 429 String jarName = signedJars.get(i); 430 try { 431 File file = Support_Resources.getExternalLocalFile(jarDirUrl 432 + "/" + jarName); 433 jarFile = new JarFile(file, true); 434 boolean foundCerts = false; 435 Enumeration<JarEntry> e = jarFile.entries(); 436 while (e.hasMoreElements()) { 437 JarEntry entry = e.nextElement(); 438 InputStream is = jarFile.getInputStream(entry); 439 is.skip(100000); 440 is.close(); 441 Certificate[] certs = entry.getCertificates(); 442 if (certs != null && certs.length > 0) { 443 foundCerts = true; 444 break; 445 } 446 } 447 assertTrue( 448 "No certificates found during signed jar test for jar \"" 449 + jarName + "\"", foundCerts); 450 } catch (IOException e) { 451 fail("Exception during signed jar test for jar \"" + jarName 452 + "\": " + e.toString()); 453 } 454 } 455 } 456 457 /** 458 * java.util.jar.JarFile#getManifest() 459 */ 460 public void test_getManifest() { 461 // Test for method java.util.jar.Manifest 462 // java.util.jar.JarFile.getManifest() 463 try { 464 Support_Resources.copyFile(resources, null, jarName); 465 JarFile jarFile = new JarFile(new File(resources, jarName)); 466 assertNotNull("Error--Manifest not returned", jarFile.getManifest()); 467 jarFile.close(); 468 } catch (Exception e) { 469 fail("Exception during 1st test: " + e.toString()); 470 } 471 try { 472 Support_Resources.copyFile(resources, null, jarName2); 473 JarFile jarFile = new JarFile(new File(resources, jarName2)); 474 assertNull("Error--should have returned null", jarFile 475 .getManifest()); 476 jarFile.close(); 477 } catch (Exception e) { 478 fail("Exception during 2nd test: " + e.toString()); 479 } 480 481 try { 482 // jarName3 was created using the following test 483 Support_Resources.copyFile(resources, null, jarName3); 484 JarFile jarFile = new JarFile(new File(resources, jarName3)); 485 assertNotNull("Should find manifest without verifying", jarFile 486 .getManifest()); 487 jarFile.close(); 488 } catch (Exception e) { 489 fail("Exception during 3rd test: " + e.toString()); 490 } 491 492 try { 493 // this is used to create jarName3 used in the previous test 494 Manifest manifest = new Manifest(); 495 Attributes attributes = manifest.getMainAttributes(); 496 attributes.put(new Attributes.Name("Manifest-Version"), "1.0"); 497 ByteArrayOutputStream manOut = new ByteArrayOutputStream(); 498 manifest.write(manOut); 499 byte[] manBytes = manOut.toByteArray(); 500 File file = File.createTempFile("hyts_manifest1", ".jar"); 501 JarOutputStream jarOut = new JarOutputStream(new FileOutputStream( 502 file.getAbsolutePath())); 503 ZipEntry entry = new ZipEntry("META-INF/"); 504 entry.setSize(0); 505 jarOut.putNextEntry(entry); 506 entry = new ZipEntry(JarFile.MANIFEST_NAME); 507 entry.setSize(manBytes.length); 508 jarOut.putNextEntry(entry); 509 jarOut.write(manBytes); 510 entry = new ZipEntry("myfile"); 511 entry.setSize(1); 512 jarOut.putNextEntry(entry); 513 jarOut.write(65); 514 jarOut.close(); 515 JarFile jar = new JarFile(file.getAbsolutePath(), false); 516 assertNotNull("Should find manifest without verifying", jar 517 .getManifest()); 518 jar.close(); 519 file.delete(); 520 } catch (IOException e) { 521 fail("IOException 3"); 522 } 523 try { 524 Support_Resources.copyFile(resources, null, jarName2); 525 JarFile jF = new JarFile(new File(resources, jarName2)); 526 jF.close(); 527 jF.getManifest(); 528 fail("FAILED: expected IllegalStateException"); 529 } catch (IllegalStateException ise) { 530 // expected; 531 } catch (Exception e) { 532 fail("Exception during 4th test: " + e.toString()); 533 } 534 535 Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); 536 JarFile jf; 537 try { 538 jf = new JarFile(new File(resources, "Broken_manifest.jar")); 539 jf.getManifest(); 540 fail("IOException expected."); 541 } catch (IOException e) { 542 // expected. 543 } 544 } 545 546 /** 547 * java.util.jar.JarFile#getInputStream(java.util.zip.ZipEntry) 548 */ 549 // This test doesn't pass on RI. If entry size is set up incorrectly, 550 // SecurityException is thrown. But SecurityException is thrown on RI only 551 // if jar file is signed incorrectly. 552 public void test_getInputStreamLjava_util_jar_JarEntry_subtest0() throws Exception { 553 File signedFile = null; 554 try { 555 Support_Resources.copyFile(resources, null, jarName4); 556 signedFile = new File(resources, jarName4); 557 } catch (Exception e) { 558 fail("Failed to create local file 2: " + e); 559 } 560 561 try { 562 JarFile jar = new JarFile(signedFile); 563 JarEntry entry = new JarEntry(entryName3); 564 InputStream in = jar.getInputStream(entry); 565 in.read(); 566 } catch (Exception e) { 567 fail("Exception during test 3: " + e); 568 } 569 570 try { 571 JarFile jar = new JarFile(signedFile); 572 JarEntry entry = new JarEntry(entryName3); 573 InputStream in = jar.getInputStream(entry); 574 // BEGIN android-added 575 byte[] dummy = getAllBytesFromStream(in); 576 // END android-added 577 assertNull("found certificates", entry.getCertificates()); 578 } catch (Exception e) { 579 fail("Exception during test 4: " + e); 580 } 581 582 try { 583 JarFile jar = new JarFile(signedFile); 584 JarEntry entry = jar.getJarEntry(entryName3); 585 entry.setSize(1076); 586 InputStream in = jar.getInputStream(entry); 587 // BEGIN android-added 588 byte[] dummy = getAllBytesFromStream(in); 589 // END android-added 590 fail("SecurityException should be thrown."); 591 } catch (SecurityException e) { 592 // expected 593 } catch (Exception e) { 594 fail("Exception during test 5: " + e); 595 } 596 597 try { 598 Support_Resources.copyFile(resources, null, jarName5); 599 signedFile = new File(resources, jarName5); 600 } catch (Exception e) { 601 fail("Failed to create local file 5: " + e); 602 } 603 604 try { 605 JarFile jar = new JarFile(signedFile); 606 JarEntry entry = new JarEntry(entryName3); 607 InputStream in = jar.getInputStream(entry); 608 fail("SecurityException should be thrown."); 609 } catch (SecurityException e) { 610 // expected 611 } catch (Exception e) { 612 fail("Exception during test 5: " + e); 613 } 614 615 // SHA1 digest, SHA256withRSA signed JAR 616 checkSignedJar(jarName6); 617 618 // SHA-256 digest, SHA256withRSA signed JAR 619 checkSignedJar(jarName7); 620 621 // SHA-512 digest, SHA512withECDSA signed JAR 622 checkSignedJar(jarName8); 623 624 // JAR with a signature that has PKCS#7 Authenticated Attributes 625 checkSignedJar(authAttrsJar); 626 627 // JAR with certificates that loop 628 checkSignedJar(certLoopJar, 3); 629 } 630 631 /** 632 * This test uses a jar file signed with an algorithm that has its own OID 633 * that is valid as a signature type. SHA256withECDSA is an algorithm that 634 * isn't combined as DigestAlgorithm + "with" + DigestEncryptionAlgorithm 635 * like RSAEncryption needs to be. 636 */ 637 public void testJarFile_Signed_Valid_DigestEncryptionAlgorithm() throws Exception { 638 checkSignedJar(jarName9); 639 } 640 641 /** 642 * Checks that a JAR is signed correctly with a signature length of 1. 643 */ 644 private void checkSignedJar(String jarName) throws Exception { 645 checkSignedJar(jarName, 1); 646 } 647 648 /** 649 * Checks that a JAR is signed correctly with a signature length of sigLength. 650 */ 651 private void checkSignedJar(String jarName, final int sigLength) throws Exception { 652 Support_Resources.copyFile(resources, null, jarName); 653 654 final File file = new File(resources, jarName); 655 656 ExecutorService executor = Executors.newSingleThreadExecutor(); 657 Future<Boolean> future = executor.submit(new Callable<Boolean>() { 658 @Override 659 public Boolean call() throws Exception { 660 JarFile jarFile = new JarFile(file, true); 661 try { 662 Enumeration<JarEntry> e = jarFile.entries(); 663 while (e.hasMoreElements()) { 664 JarEntry entry = e.nextElement(); 665 InputStream is = jarFile.getInputStream(entry); 666 is.skip(100000); 667 is.close(); 668 Certificate[] certs = entry.getCertificates(); 669 if (certs != null && certs.length > 0) { 670 assertEquals(sigLength, certs.length); 671 return true; 672 } 673 } 674 return false; 675 } finally { 676 jarFile.close(); 677 } 678 } 679 }); 680 executor.shutdown(); 681 final boolean foundCerts; 682 try { 683 foundCerts = future.get(10, TimeUnit.SECONDS); 684 } catch (TimeoutException e) { 685 fail("Could not finish building chain; possibly confused by loops"); 686 return; // Not actually reached. 687 } 688 689 assertTrue( 690 "No certificates found during signed jar test for jar \"" 691 + jarName + "\"", foundCerts); 692 } 693 694 private static class Results { 695 public Certificate[] certificates; 696 public CodeSigner[] signers; 697 } 698 699 private Results getSignedJarCerts(String jarName) throws Exception { 700 Support_Resources.copyFile(resources, null, jarName); 701 702 File file = new File(resources, jarName); 703 Results results = new Results(); 704 705 JarFile jarFile = new JarFile(file, true, ZipFile.OPEN_READ); 706 try { 707 708 Enumeration<JarEntry> e = jarFile.entries(); 709 while (e.hasMoreElements()) { 710 JarEntry entry = e.nextElement(); 711 InputStream is = jarFile.getInputStream(entry); 712 // Skip bytes because we have to read the entire file for it to read signatures. 713 is.skip(entry.getSize()); 714 is.close(); 715 Certificate[] certs = entry.getCertificates(); 716 CodeSigner[] signers = entry.getCodeSigners(); 717 if (certs != null && certs.length > 0) { 718 results.certificates = certs; 719 results.signers = signers; 720 break; 721 } 722 } 723 } finally { 724 jarFile.close(); 725 } 726 727 return results; 728 } 729 730 public void testJarFile_Signed_ValidChain() throws Exception { 731 Results result = getSignedJarCerts(VALID_CHAIN_JAR); 732 assertNotNull(result); 733 assertEquals(Arrays.deepToString(result.certificates), 3, result.certificates.length); 734 assertEquals(Arrays.deepToString(result.signers), 1, result.signers.length); 735 assertEquals(3, result.signers[0].getSignerCertPath().getCertificates().size()); 736 assertEquals("CN=fake-chain", ((X509Certificate) result.certificates[0]).getSubjectDN().toString()); 737 assertEquals("CN=intermediate1", ((X509Certificate) result.certificates[1]).getSubjectDN().toString()); 738 assertEquals("CN=root1", ((X509Certificate) result.certificates[2]).getSubjectDN().toString()); 739 } 740 741 public void testJarFile_Signed_InvalidChain() throws Exception { 742 Results result = getSignedJarCerts(INVALID_CHAIN_JAR); 743 assertNotNull(result); 744 assertEquals(Arrays.deepToString(result.certificates), 3, result.certificates.length); 745 assertEquals(Arrays.deepToString(result.signers), 1, result.signers.length); 746 assertEquals(3, result.signers[0].getSignerCertPath().getCertificates().size()); 747 assertEquals("CN=fake-chain", ((X509Certificate) result.certificates[0]).getSubjectDN().toString()); 748 assertEquals("CN=intermediate1", ((X509Certificate) result.certificates[1]).getSubjectDN().toString()); 749 assertEquals("CN=root1", ((X509Certificate) result.certificates[2]).getSubjectDN().toString()); 750 } 751 752 public void testJarFile_Signed_AmbiguousSigners() throws Exception { 753 Results result = getSignedJarCerts(AMBIGUOUS_SIGNERS_JAR); 754 assertNotNull(result); 755 assertEquals(Arrays.deepToString(result.certificates), 2, result.certificates.length); 756 assertEquals(Arrays.deepToString(result.signers), 2, result.signers.length); 757 assertEquals(1, result.signers[0].getSignerCertPath().getCertificates().size()); 758 assertEquals(1, result.signers[1].getSignerCertPath().getCertificates().size()); 759 } 760 761 /* 762 * The jar created by 1.4 which does not provide a 763 * algorithm-Digest-Manifest-Main-Attributes entry in .SF file. 764 */ 765 public void test_Jar_created_before_java_5() throws IOException { 766 String modifiedJarName = "Created_by_1_4.jar"; 767 Support_Resources.copyFile(resources, null, modifiedJarName); 768 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 769 true); 770 Enumeration<JarEntry> entries = jarFile.entries(); 771 while (entries.hasMoreElements()) { 772 ZipEntry zipEntry = entries.nextElement(); 773 jarFile.getInputStream(zipEntry); 774 } 775 } 776 777 /* The jar is intact, then everything is all right. */ 778 public void test_JarFile_Integrate_Jar() throws IOException { 779 String modifiedJarName = "Integrate.jar"; 780 Support_Resources.copyFile(resources, null, modifiedJarName); 781 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 782 true); 783 Enumeration<JarEntry> entries = jarFile.entries(); 784 while (entries.hasMoreElements()) { 785 ZipEntry zipEntry = entries.nextElement(); 786 jarFile.getInputStream(zipEntry).skip(Long.MAX_VALUE); 787 } 788 } 789 790 /** 791 * The jar is intact, but the entry object is modified. 792 */ 793 public void testJarVerificationModifiedEntry() throws IOException { 794 Support_Resources.copyFile(resources, null, integrateJar); 795 File f = new File(resources, integrateJar); 796 797 JarFile jarFile = new JarFile(f); 798 ZipEntry zipEntry = jarFile.getJarEntry(integrateJarEntry); 799 zipEntry.setSize(zipEntry.getSize() + 1); 800 jarFile.getInputStream(zipEntry).skip(Long.MAX_VALUE); 801 802 jarFile = new JarFile(f); 803 zipEntry = jarFile.getJarEntry(integrateJarEntry); 804 zipEntry.setSize(zipEntry.getSize() - 1); 805 try { 806 //jarFile.getInputStream(zipEntry).skip(Long.MAX_VALUE); 807 jarFile.getInputStream(zipEntry).read(new byte[5000], 0, 5000); 808 fail("SecurityException expected"); 809 } catch (SecurityException e) { 810 // desired 811 } 812 } 813 814 /* 815 * If another entry is inserted into Manifest, no security exception will be 816 * thrown out. 817 */ 818 public void test_JarFile_InsertEntry_in_Manifest_Jar() throws IOException { 819 String modifiedJarName = "Inserted_Entry_Manifest.jar"; 820 Support_Resources.copyFile(resources, null, modifiedJarName); 821 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 822 true); 823 Enumeration<JarEntry> entries = jarFile.entries(); 824 int count = 0; 825 while (entries.hasMoreElements()) { 826 827 ZipEntry zipEntry = entries.nextElement(); 828 jarFile.getInputStream(zipEntry); 829 count++; 830 } 831 assertEquals(5, count); 832 } 833 834 /* 835 * If another entry is inserted into Manifest, no security exception will be 836 * thrown out. 837 */ 838 public void test_Inserted_Entry_Manifest_with_DigestCode() 839 throws IOException { 840 String modifiedJarName = "Inserted_Entry_Manifest_with_DigestCode.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 ZipEntry zipEntry = entries.nextElement(); 848 jarFile.getInputStream(zipEntry); 849 count++; 850 } 851 assertEquals(5, count); 852 } 853 854 /* 855 * The content of Test.class is modified, jarFile.getInputStream will not 856 * throw security Exception, but it will anytime before the inputStream got 857 * from getInputStream method has been read to end. 858 */ 859 public void test_JarFile_Modified_Class() throws IOException { 860 String modifiedJarName = "Modified_Class.jar"; 861 Support_Resources.copyFile(resources, null, modifiedJarName); 862 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 863 true); 864 Enumeration<JarEntry> entries = jarFile.entries(); 865 while (entries.hasMoreElements()) { 866 ZipEntry zipEntry = entries.nextElement(); 867 jarFile.getInputStream(zipEntry); 868 } 869 /* The content of Test.class has been tampered. */ 870 ZipEntry zipEntry = jarFile.getEntry("Test.class"); 871 InputStream in = jarFile.getInputStream(zipEntry); 872 byte[] buffer = new byte[1024]; 873 try { 874 while (in.available() > 0) { 875 in.read(buffer); 876 } 877 fail("SecurityException expected"); 878 } catch (SecurityException e) { 879 // desired 880 } 881 } 882 883 /* 884 * In the Modified.jar, the main attributes of META-INF/MANIFEST.MF is 885 * tampered manually. Hence the RI 5.0 JarFile.getInputStream of any 886 * JarEntry will throw security exception. 887 */ 888 public void test_JarFile_Modified_Manifest_MainAttributes() 889 throws IOException { 890 String modifiedJarName = "Modified_Manifest_MainAttributes.jar"; 891 Support_Resources.copyFile(resources, null, modifiedJarName); 892 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 893 true); 894 Enumeration<JarEntry> entries = jarFile.entries(); 895 while (entries.hasMoreElements()) { 896 ZipEntry zipEntry = entries.nextElement(); 897 try { 898 jarFile.getInputStream(zipEntry); 899 fail("SecurityException expected"); 900 } catch (SecurityException e) { 901 // desired 902 } 903 } 904 } 905 906 /* 907 * It is all right in our original JarFile. If the Entry Attributes, for 908 * example Test.class in our jar, the jarFile.getInputStream will throw 909 * Security Exception. 910 */ 911 public void test_JarFile_Modified_Manifest_EntryAttributes() 912 throws IOException { 913 String modifiedJarName = "Modified_Manifest_EntryAttributes.jar"; 914 Support_Resources.copyFile(resources, null, modifiedJarName); 915 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 916 true); 917 Enumeration<JarEntry> entries = jarFile.entries(); 918 while (entries.hasMoreElements()) { 919 ZipEntry zipEntry = entries.nextElement(); 920 try { 921 jarFile.getInputStream(zipEntry); 922 fail("should throw Security Exception"); 923 } catch (SecurityException e) { 924 // desired 925 } 926 } 927 } 928 929 /* 930 * If the content of the .SA file is modified, no matter what it resides, 931 * JarFile.getInputStream of any JarEntry will throw Security Exception. 932 */ 933 public void test_JarFile_Modified_SF_EntryAttributes() throws IOException { 934 String modifiedJarName = "Modified_SF_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 public void test_close() throws IOException { 951 String modifiedJarName = "Modified_SF_EntryAttributes.jar"; 952 Support_Resources.copyFile(resources, null, modifiedJarName); 953 JarFile jarFile = new JarFile(new File(resources, modifiedJarName), 954 true); 955 Enumeration<JarEntry> entries = jarFile.entries(); 956 957 jarFile.close(); 958 jarFile.close(); 959 960 // Can not check IOException 961 } 962 963 /** 964 * @throws IOException 965 * java.util.jar.JarFile#getInputStream(java.util.zip.ZipEntry) 966 */ 967 public void test_getInputStreamLjava_util_jar_JarEntry() throws IOException { 968 File localFile = null; 969 try { 970 Support_Resources.copyFile(resources, null, jarName); 971 localFile = new File(resources, jarName); 972 } catch (Exception e) { 973 fail("Failed to create local file: " + e); 974 } 975 976 byte[] b = new byte[1024]; 977 try { 978 JarFile jf = new JarFile(localFile); 979 java.io.InputStream is = jf.getInputStream(jf.getEntry(entryName)); 980 // BEGIN android-removed 981 // jf.close(); 982 // END android-removed 983 assertTrue("Returned invalid stream", is.available() > 0); 984 int r = is.read(b, 0, 1024); 985 is.close(); 986 StringBuffer sb = new StringBuffer(r); 987 for (int i = 0; i < r; i++) { 988 sb.append((char) (b[i] & 0xff)); 989 } 990 String contents = sb.toString(); 991 assertTrue("Incorrect stream read", contents.indexOf("bar") > 0); 992 // BEGIN android-added 993 jf.close(); 994 // END android-added 995 } catch (Exception e) { 996 fail("Exception during test: " + e.toString()); 997 } 998 999 try { 1000 JarFile jf = new JarFile(localFile); 1001 InputStream in = jf.getInputStream(new JarEntry("invalid")); 1002 assertNull("Got stream for non-existent entry", in); 1003 } catch (Exception e) { 1004 fail("Exception during test 2: " + e); 1005 } 1006 1007 try { 1008 Support_Resources.copyFile(resources, null, jarName); 1009 File signedFile = new File(resources, jarName); 1010 JarFile jf = new JarFile(signedFile); 1011 JarEntry jre = new JarEntry("foo/bar/A.class"); 1012 jf.getInputStream(jre); 1013 // InputStream returned in any way, exception can be thrown in case 1014 // of reading from this stream only. 1015 // fail("Should throw ZipException"); 1016 } catch (ZipException ee) { 1017 // expected 1018 } 1019 1020 try { 1021 Support_Resources.copyFile(resources, null, jarName); 1022 File signedFile = new File(resources, jarName); 1023 JarFile jf = new JarFile(signedFile); 1024 JarEntry jre = new JarEntry("foo/bar/A.class"); 1025 jf.close(); 1026 jf.getInputStream(jre); 1027 // InputStream returned in any way, exception can be thrown in case 1028 // of reading from this stream only. 1029 // The same for IOException 1030 fail("Should throw IllegalStateException"); 1031 } catch (IllegalStateException ee) { 1032 // expected 1033 } 1034 } 1035 1036 /** 1037 * The jar is intact, but the entry object is modified. 1038 */ 1039 // Regression test for issue introduced by HARMONY-4569: signed archives containing files with size 0 could not get verified. 1040 public void testJarVerificationEmptyEntry() throws IOException { 1041 Support_Resources.copyFile(resources, null, emptyEntryJar); 1042 File f = new File(resources, emptyEntryJar); 1043 1044 JarFile jarFile = new JarFile(f); 1045 1046 ZipEntry zipEntry = jarFile.getJarEntry(emptyEntry1); 1047 int res = jarFile.getInputStream(zipEntry).read(new byte[100], 0, 100); 1048 assertEquals("Wrong length of empty jar entry", -1, res); 1049 1050 zipEntry = jarFile.getJarEntry(emptyEntry2); 1051 res = jarFile.getInputStream(zipEntry).read(new byte[100], 0, 100); 1052 assertEquals("Wrong length of empty jar entry", -1, res); 1053 1054 zipEntry = jarFile.getJarEntry(emptyEntry3); 1055 res = jarFile.getInputStream(zipEntry).read(); 1056 assertEquals("Wrong length of empty jar entry", -1, res); 1057 } 1058 1059 public void testJarFile_BadSignatureProvider_Success() throws Exception { 1060 Security.insertProviderAt(new JarFileBadProvider(), 1); 1061 try { 1062 // Needs a JAR with "RSA" as digest encryption algorithm 1063 checkSignedJar(jarName6); 1064 } finally { 1065 Security.removeProvider(JarFileBadProvider.NAME); 1066 } 1067 } 1068 1069 public static class JarFileBadProvider extends Provider { 1070 public static final String NAME = "JarFileBadProvider"; 1071 1072 public JarFileBadProvider() { 1073 super(NAME, 1.0, "Bad provider for JarFileTest"); 1074 1075 put("Signature.RSA", NotReallyASignature.class.getName()); 1076 } 1077 1078 /** 1079 * This should never be instantiated, so everything throws an exception. 1080 */ 1081 public static class NotReallyASignature extends SignatureSpi { 1082 @Override 1083 protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { 1084 fail("Should not call this provider"); 1085 } 1086 1087 @Override 1088 protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { 1089 fail("Should not call this provider"); 1090 } 1091 1092 @Override 1093 protected void engineUpdate(byte b) throws SignatureException { 1094 fail("Should not call this provider"); 1095 } 1096 1097 @Override 1098 protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { 1099 fail("Should not call this provider"); 1100 } 1101 1102 @Override 1103 protected byte[] engineSign() throws SignatureException { 1104 fail("Should not call this provider"); 1105 return null; 1106 } 1107 1108 @Override 1109 protected boolean engineVerify(byte[] sigBytes) throws SignatureException { 1110 fail("Should not call this provider"); 1111 return false; 1112 } 1113 1114 @Override 1115 protected void engineSetParameter(String param, Object value) 1116 throws InvalidParameterException { 1117 fail("Should not call this provider"); 1118 } 1119 1120 @Override 1121 protected Object engineGetParameter(String param) throws InvalidParameterException { 1122 fail("Should not call this provider"); 1123 return null; 1124 } 1125 } 1126 } 1127} 1128