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