Home | History | Annotate | Download | only in jar
      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