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