1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug 8031572
  27  * @summary jarsigner -verify exits with 0 when a jar file is not properly signed
  28  * @library /lib/testlibrary
  29  * @build jdk.testlibrary.IOUtils
  30  * @run main EntriesOrder
  31  */
  32 
  33 import java.io.FileInputStream;
  34 import java.io.FileOutputStream;
  35 import java.nio.file.Files;
  36 import java.nio.file.Paths;
  37 import java.security.cert.Certificate;
  38 import java.util.*;
  39 import java.util.jar.JarEntry;
  40 import java.util.jar.JarFile;
  41 import java.util.jar.JarInputStream;
  42 import java.util.zip.ZipEntry;
  43 import java.util.zip.ZipOutputStream;
  44 
  45 import jdk.testlibrary.IOUtils;
  46 
  47 public class EntriesOrder {
  48 
  49     public static void main(String[] args) throws Exception {
  50 
  51         String[] entries = {
  52                 "META-INF/",
  53                 "META-INF/MANIFEST.MF",
  54                 "META-INF/A.RSA",
  55                 "META-INF/A.SF",
  56                 "META-INF/inf",
  57                 "a"};
  58 
  59         Map<String,byte[]> content = new HashMap<>();
  60 
  61         // We will create a jar containing entries above. Try all permutations
  62         // and confirm 1) When opened as a JarFile, we can always get 3 signed
  63         // ones (MANIFEST, inf, a), and 2) When opened as a JarInputStream,
  64         // when the order is correct (MANIFEST at beginning, followed by RSA/SF,
  65         // directory ignored), we can get 2 signed ones (inf, a).
  66 
  67         // Prepares raw files
  68         Files.write(Paths.get("a"), "a".getBytes());
  69         Files.createDirectory(Paths.get("META-INF/"));
  70         Files.write(Paths.get("META-INF/inf"), "inf".getBytes());
  71 
  72         // Pack, sign, and extract to get all files
  73         sun.tools.jar.Main m =
  74                 new sun.tools.jar.Main(System.out, System.err, "jar");
  75         if (!m.run("cvf a.jar a META-INF/inf".split(" "))) {
  76             throw new Exception("jar creation failed");
  77         }
  78         sun.security.tools.keytool.Main.main(
  79                 ("-keystore jks -storepass changeit -keypass changeit -dname" +
  80                         " CN=A -alias a -genkeypair -keyalg rsa").split(" "));
  81         sun.security.tools.jarsigner.Main.main(
  82                 "-keystore jks -storepass changeit a.jar a".split(" "));
  83         m = new sun.tools.jar.Main(System.out, System.err, "jar");
  84         if (!m.run("xvf a.jar".split(" "))) {
  85             throw new Exception("jar extraction failed");
  86         }
  87 
  88         // Data
  89         for (String s: entries) {
  90             if (!s.endsWith("/")) {
  91                 content.put(s, Files.readAllBytes(Paths.get(s)));
  92             }
  93         }
  94 
  95         // Test
  96         for (List<String> perm: Permute(entries)) {
  97 
  98             // Recreate a jar
  99             try (ZipOutputStream zos
 100                          = new ZipOutputStream(new FileOutputStream("x.jar"))) {
 101                 for (String e: perm) {
 102                     zos.putNextEntry(new ZipEntry(e));
 103                     if (Paths.get(e).toFile().isDirectory()) continue;
 104                     zos.write(content.get(e));
 105                 }
 106             }
 107 
 108             // Open with JarFile, number of signed entries should be 3.
 109             int cc = 0;
 110             try (JarFile jf = new JarFile("x.jar")) {
 111                 Enumeration<JarEntry> jes = jf.entries();
 112                 while (jes.hasMoreElements()) {
 113                     JarEntry je = jes.nextElement();
 114                     IOUtils.readFully(jf.getInputStream(je));
 115                     Certificate[] certs = je.getCertificates();
 116                     if (certs != null && certs.length > 0) {
 117                         cc++;
 118                     }
 119                 }
 120             }
 121 
 122             if (cc != 3) {
 123                 System.out.println(perm + " - jf - " + cc);
 124                 throw new Exception();
 125             }
 126 
 127             // Open with JarInputStream
 128             int signed;
 129 
 130             perm.remove("META-INF/");
 131             if (perm.get(0).equals("META-INF/MANIFEST.MF") &&
 132                     perm.get(1).contains("/A.") &&
 133                     perm.get(2).contains("/A.")) {
 134                 signed = 2;     // Good order
 135             } else {
 136                 signed = 0;     // Bad order. In this case, the number of signed
 137                                 // entries is not documented. Just test impl.
 138             }
 139 
 140             cc = 0;
 141             try (JarInputStream jis
 142                          = new JarInputStream(new FileInputStream("x.jar"))) {
 143                 while (true) {
 144                     JarEntry je = jis.getNextJarEntry();
 145                     if (je == null) break;
 146                     IOUtils.readFully(jis);
 147                     Certificate[] certs = je.getCertificates();
 148                     if (certs != null && certs.length > 0) {
 149                         cc++;
 150                     }
 151                 }
 152             }
 153 
 154             if (cc != signed) {
 155                 System.out.println(perm + " - jis - " + cc + " " + signed);
 156                 throw new Exception();
 157             }
 158         }
 159     }
 160 
 161     // Helper method to return all permutations of an array. Each output can
 162     // be altered without damaging the iteration process.
 163     static Iterable<List<String>> Permute(String[] entries) {
 164         return new Iterable<List<String>>() {
 165 
 166             int s = entries.length;
 167             long c = factorial(s) - 1;      // number of permutations
 168 
 169             private long factorial(int n) {
 170                 return (n == 1) ? 1: (n * factorial(n-1));
 171             }
 172 
 173             @Override
 174             public Iterator<List<String>> iterator() {
 175                 return new Iterator<List<String>>() {
 176                     @Override
 177                     public boolean hasNext() {
 178                         return c >= 0;
 179                     }
 180 
 181                     @Override
 182                     public List<String> next() {
 183                         if (c < 0) return null;
 184                         List<String> result = new ArrayList<>(s);
 185                         LinkedList<String> source = new LinkedList<>(
 186                                 Arrays.asList(entries));
 187                         // Treat c as a integer with different radixes at
 188                         // different digits, i.e. at digit 0, radix is s;
 189                         // at digit 1, radix is s-1. Thus a s-digit number
 190                         // is able to represent s! different values.
 191                         long n = c;
 192                         for (int i=s; i>=1; i--) {
 193                             int x = (int)(n % i);
 194                             result.add(source.remove(x));
 195                             n = n / i;
 196                         }
 197                         c--;
 198                         return result;
 199                     }
 200                 };
 201             }
 202         };
 203     }
 204 }