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