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