8159393: jlink should print a warning that a signed modular JAR will be treated as unsigned
authorjlaskey
Mon, 07 Nov 2016 13:10:42 -0400
changeset 41919 612b698f8b8f
parent 41918 75bfe1e98bf3
child 41920 bbbca8837d8f
8159393: jlink should print a warning that a signed modular JAR will be treated as unsigned Reviewed-by: alanb, sundar, mullan, weijun
jdk/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties
jdk/test/tools/jlink/JLinkSigningTest.java
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java	Mon Nov 07 14:15:49 2016 +0100
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java	Mon Nov 07 13:10:42 2016 -0400
@@ -359,9 +359,9 @@
         if (name.endsWith(".class") && !name.endsWith("module-info.class")) {
             try {
                 byte[] bytes = reader.getResource(location);
-                ClassReader cr =new ClassReader(bytes);
+                ClassReader cr = new ClassReader(bytes);
                 ClassNode cn = new ClassNode();
-                cr.accept(cn, ClassReader.EXPAND_FRAMES);
+                cr.accept(cn, 0);
             } catch (Exception ex) {
                 log.println("Error(s) in Class: " + name);
             }
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java	Mon Nov 07 14:15:49 2016 +0100
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java	Mon Nov 07 13:10:42 2016 -0400
@@ -33,7 +33,6 @@
 import java.lang.module.ModuleReference;
 import java.lang.module.ResolutionException;
 import java.lang.module.ResolvedModule;
-import java.lang.reflect.InvocationTargetException;
 import java.net.URI;
 import java.nio.ByteOrder;
 import java.nio.file.Files;
@@ -41,6 +40,7 @@
 import java.nio.file.Paths;
 import java.util.*;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import jdk.tools.jlink.internal.TaskHelper.BadArgs;
 import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE;
@@ -62,20 +62,8 @@
 public class JlinkTask {
     static final boolean DEBUG = Boolean.getBoolean("jlink.debug");
 
-    private static <T extends Throwable> void fail(Class<T> type,
-            String format,
-            Object... args) throws T {
-        String msg = new Formatter().format(format, args).toString();
-        try {
-            T t = type.getConstructor(String.class).newInstance(msg);
-            throw t;
-        } catch (InstantiationException |
-                InvocationTargetException |
-                NoSuchMethodException |
-                IllegalAccessException e) {
-            throw new InternalError("Unable to create an instance of " + type, e);
-        }
-    }
+    // jlink API ignores by default. Remove when signing is implemented.
+    static final boolean IGNORE_SIGNING_DEFAULT = true;
 
     private static final TaskHelper taskHelper
             = new TaskHelper(JLINK_BUNDLE);
@@ -143,7 +131,10 @@
         }, "--save-opts"),
         new Option<JlinkTask>(false, (task, opt, arg) -> {
             task.options.fullVersion = true;
-        }, true, "--full-version"),};
+        }, true, "--full-version"),
+        new Option<JlinkTask>(false, (task, opt, arg) -> {
+            task.options.ignoreSigning = true;
+        }, true, "--ignore-signing-information"),};
 
     private static final String PROGNAME = "jlink";
     private final OptionsValues options = new OptionsValues();
@@ -160,7 +151,8 @@
     /**
      * Result codes.
      */
-    static final int EXIT_OK = 0, // Completed with no errors.
+    static final int
+            EXIT_OK = 0, // Completed with no errors.
             EXIT_ERROR = 1, // Completed but reported errors.
             EXIT_CMDERR = 2, // Bad command-line arguments
             EXIT_SYSERR = 3, // System error or resource exhaustion.
@@ -171,12 +163,13 @@
         String  saveoptsfile;
         boolean version;
         boolean fullVersion;
-        List<Path> modulePath = new ArrayList<>();
-        Set<String> limitMods = new HashSet<>();
-        Set<String> addMods = new HashSet<>();
+        final List<Path> modulePath = new ArrayList<>();
+        final Set<String> limitMods = new HashSet<>();
+        final Set<String> addMods = new HashSet<>();
         Path output;
         Path packagedModulesPath;
         ByteOrder endian = ByteOrder.nativeOrder();
+        boolean ignoreSigning = false;
     }
 
     int run(String[] args) {
@@ -199,7 +192,7 @@
                 return EXIT_OK;
             }
             if (taskHelper.getExistingImage() == null) {
-                if (options.modulePath == null || options.modulePath.isEmpty()) {
+                if (options.modulePath.isEmpty()) {
                     throw taskHelper.newBadArgs("err.modulepath.must.be.specified").showUsage(true);
                 }
                 createImage();
@@ -248,20 +241,25 @@
         plugins = plugins == null ? new PluginsConfiguration() : plugins;
 
         if (config.getModulepaths().isEmpty()) {
-            throw new Exception("Empty module paths");
+            throw new IllegalArgumentException("Empty module paths");
         }
 
         ModuleFinder finder = newModuleFinder(config.getModulepaths(),
                                               config.getLimitmods(),
                                               config.getModules());
 
+        if (config.getModules().isEmpty()) {
+            throw new IllegalArgumentException("No modules to add");
+        }
+
         // First create the image provider
-        ImageProvider imageProvider
-                = createImageProvider(finder,
-                                      checkAddMods(config.getModules()),
-                                      config.getLimitmods(),
-                                      config.getByteOrder(),
-                                      null);
+        ImageProvider imageProvider =
+                createImageProvider(finder,
+                                    config.getModules(),
+                                    config.getLimitmods(),
+                                    config.getByteOrder(),
+                                    null,
+                                    IGNORE_SIGNING_DEFAULT);
 
         // Then create the Plugin Stack
         ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins);
@@ -299,19 +297,17 @@
         }
         ModuleFinder finder
                 = newModuleFinder(options.modulePath, options.limitMods, options.addMods);
-        try {
-            options.addMods = checkAddMods(options.addMods);
-        } catch (IllegalArgumentException ex) {
+        if (options.addMods.isEmpty()) {
             throw taskHelper.newBadArgs("err.mods.must.be.specified", "--add-modules")
                     .showUsage(true);
         }
         // First create the image provider
-        ImageProvider imageProvider
-                = createImageProvider(finder,
+        ImageProvider imageProvider = createImageProvider(finder,
                         options.addMods,
                         options.limitMods,
                         options.endian,
-                        options.packagedModulesPath);
+                        options.packagedModulesPath,
+                        options.ignoreSigning);
 
         // Then create the Plugin Stack
         ImagePluginStack stack = ImagePluginConfiguration.
@@ -321,13 +317,6 @@
         stack.operate(imageProvider);
     }
 
-    private static Set<String> checkAddMods(Set<String> addMods) {
-        if (addMods.isEmpty()) {
-            throw new IllegalArgumentException("no modules to add");
-        }
-        return addMods;
-    }
-
     /**
      * Returns a module finder to find the observable modules specified in
      * the --module-path and --limit-modules options
@@ -343,7 +332,7 @@
         return finder;
     }
 
-    /**
+    /*
      * Returns a module finder of the given module path that limits
      * the observable modules to those in the transitive closure of
      * the modules specified in {@code limitMods} plus other modules
@@ -376,7 +365,8 @@
                                                      Set<String> addMods,
                                                      Set<String> limitMods,
                                                      ByteOrder order,
-                                                     Path retainModulesPath)
+                                                     Path retainModulesPath,
+                                                     boolean ignoreSigning)
             throws IOException
     {
         if (addMods.isEmpty()) {
@@ -390,10 +380,10 @@
 
         Map<String, Path> mods = cf.modules().stream()
             .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation));
-        return new ImageHelper(cf, mods, order, retainModulesPath);
+        return new ImageHelper(cf, mods, order, retainModulesPath, ignoreSigning);
     }
 
-    /**
+    /*
      * Returns a ModuleFinder that limits observability to the given root
      * modules, their transitive dependences, plus a set of other modules.
      */
@@ -477,36 +467,57 @@
     }
 
     private static class ImageHelper implements ImageProvider {
-
-        final Set<Archive> archives;
         final ByteOrder order;
         final Path packagedModulesPath;
+        final boolean ignoreSigning;
+        final Set<Archive> archives;
 
         ImageHelper(Configuration cf,
                     Map<String, Path> modsPaths,
                     ByteOrder order,
-                    Path packagedModulesPath) throws IOException {
-            archives = modsPaths.entrySet().stream()
+                    Path packagedModulesPath,
+                    boolean ignoreSigning) throws IOException {
+            this.order = order;
+            this.packagedModulesPath = packagedModulesPath;
+            this.ignoreSigning = ignoreSigning;
+            this.archives = modsPaths.entrySet().stream()
                                 .map(e -> newArchive(e.getKey(), e.getValue()))
                                 .collect(Collectors.toSet());
-            this.order = order;
-            this.packagedModulesPath = packagedModulesPath;
         }
 
         private Archive newArchive(String module, Path path) {
             if (path.toString().endsWith(".jmod")) {
                 return new JmodArchive(module, path);
             } else if (path.toString().endsWith(".jar")) {
-                return new ModularJarArchive(module, path);
+                ModularJarArchive modularJarArchive = new ModularJarArchive(module, path);
+
+                Stream<Archive.Entry> signatures = modularJarArchive.entries().filter((entry) -> {
+                    String name = entry.name().toUpperCase(Locale.ENGLISH);
+
+                    return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && (
+                                name.endsWith(".SF") ||
+                                name.endsWith(".DSA") ||
+                                name.endsWith(".RSA") ||
+                                name.endsWith(".EC") ||
+                                name.startsWith("META-INF/SIG-")
+                            );
+                });
+
+                if (signatures.count() != 0) {
+                    if (ignoreSigning) {
+                        System.err.println(taskHelper.getMessage("warn.signing", path));
+                    } else {
+                        throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path));
+                    }
+                }
+
+                return modularJarArchive;
             } else if (Files.isDirectory(path)) {
                 return new DirArchive(path);
             } else {
-                fail(RuntimeException.class,
-                        "Selected module %s (%s) not in jmod or modular jar format",
-                        module,
-                        path);
+                throw new IllegalArgumentException(
+                    taskHelper.getMessage("err.not.modular.format", module, path));
             }
-            return null;
         }
 
         @Override
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java	Mon Nov 07 14:15:49 2016 +0100
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java	Mon Nov 07 13:10:42 2016 -0400
@@ -212,7 +212,7 @@
             mainOptions.add(new PlugOption(true, (task, opt, arg) -> {
                 Path path = Paths.get(arg);
                 if (!Files.exists(path) || !Files.isDirectory(path)) {
-                    throw newBadArgs("err.existing.image.must.exist");
+                    throw newBadArgs("err.image.must.exist");
                 }
                 existingImage = path.toAbsolutePath();
             }, true, POST_PROCESS));
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties	Mon Nov 07 14:15:49 2016 +0100
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties	Mon Nov 07 13:10:42 2016 -0400
@@ -62,6 +62,9 @@
 main.opt.save-opts=\
 \  --save-opts <filename>            Save jlink options in the given file
 
+main.opt.ignore-signing-information=\
+\  --ignore-signing-information      Ignore signing information in modular JARs
+
 main.msg.bug=\
 An exception has occurred in jlink. \
 Please file a bug at the Java Bug Database (http://bugreport.java.com/bugreport/) \
@@ -88,7 +91,7 @@
 err.mods.must.be.specified:no modules specified to {0}
 err.path.not.found=path not found: {0}
 err.path.not.valid=invalid path: {0}
-err.existing.image.must.exist=existing image doesn't exists or is not a directory
+err.image.must.exist=image does not exist or is not a directory
 err.existing.image.invalid=existing image is not valid
 err.file.not.found=cannot find file: {0}
 err.file.error=cannot access file: {0}
@@ -104,5 +107,9 @@
 err.config.defaults=property {0} is missing from configuration
 err.config.defaults.value=wrong value in defaults property: {0}
 err.bom.generation=bom file generation failed: {0}
-warn.invalid.arg=Invalid classname or pathname not exist: {0}
+err.not.modular.format=selected module {0} ({1}) not in jmod or modular JAR format
+err.signing=signed modular JAR {0} is currently not supported,\
+\ use --ignore-signing-information to suppress error
+warn.signing=signed modular JAR {0} is currently not supported
+warn.invalid.arg=invalid classname or pathname not exist: {0}
 warn.split.package=package {0} defined in {1} {2}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/JLinkSigningTest.java	Mon Nov 07 13:10:42 2016 -0400
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8159393
+ * @summary Test signed jars involved in image creation
+ * @modules java.base/jdk.internal.jimage
+ *          jdk.jlink/jdk.tools.jlink.internal
+ *          jdk.compiler/com.sun.tools.javac
+ *          java.base/sun.security.tools.keytool
+ *          jdk.jartool/sun.security.tools.jarsigner
+ *          jdk.jartool/sun.tools.jar
+ * @run main/othervm JLinkSigningTest
+ */
+
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public class JLinkSigningTest {
+    static final String[] MODULE_INFO = {
+        "module test {",
+        "}",
+    };
+
+    static final String[] TEST_CLASS = {
+        "package test;",
+        "public class test {",
+        "    public static void main(String[] args) {",
+        "    }",
+        "}",
+    };
+
+    static void report(String command, String[] args) {
+        System.out.println(command + " " + String.join(" ", Arrays.asList(args)));
+    }
+
+    static void javac(String[] args) {
+        report("javac", args);
+        com.sun.tools.javac.Main javac = new com.sun.tools.javac.Main();
+
+        if (javac.compile(args) != 0) {
+            throw new RuntimeException("javac failed");
+        }
+    }
+
+    static void jar(String[] args) {
+        report("jar", args);
+        sun.tools.jar.Main jar = new sun.tools.jar.Main(System.out, System.err, "jar");
+
+        if (!jar.run(args)) {
+            throw new RuntimeException("jar failed");
+        }
+    }
+
+    static void keytool(String[] args) {
+        report("keytool", args);
+
+        try {
+            sun.security.tools.keytool.Main.main(args);
+        } catch (Exception ex) {
+            throw new RuntimeException("keytool failed");
+        }
+    }
+
+    static void jarsigner(String[] args) {
+        report("jarsigner", args);
+
+        try {
+            sun.security.tools.jarsigner.Main.main(args);
+        } catch (Exception ex) {
+            throw new RuntimeException("jarsigner failed");
+        }
+    }
+
+    static void jlink(String[] args) {
+        report("jlink", args);
+
+        try {
+            jdk.tools.jlink.internal.Main.run(new PrintWriter(System.out, true),
+                                              new PrintWriter(System.err, true),
+                                              args);
+        } catch (Exception ex) {
+            throw new RuntimeException("jlink failed");
+        }
+    }
+
+    public static void main(String[] args) {
+        final String JAVA_HOME = System.getProperty("java.home");
+        Path moduleInfoJavaPath = Paths.get("module-info.java");
+        Path moduleInfoClassPath = Paths.get("module-info.class");
+        Path testDirectoryPath = Paths.get("test");
+        Path testJavaPath = testDirectoryPath.resolve("test.java");
+        Path testClassPath = testDirectoryPath.resolve("test.class");
+        Path testModsDirectoryPath = Paths.get("testmods");
+        Path jmodsPath = Paths.get(JAVA_HOME, "jmods");
+        Path testjarPath = testModsDirectoryPath.resolve("test.jar");
+        String modulesPath = testjarPath.toString() +
+                             File.pathSeparator +
+                             jmodsPath.toString();
+
+        try {
+            Files.write(moduleInfoJavaPath, Arrays.asList(MODULE_INFO));
+            Files.createDirectories(testDirectoryPath);
+            Files.write(testJavaPath, Arrays.asList(TEST_CLASS));
+            Files.createDirectories(testModsDirectoryPath);
+        } catch (IOException ex) {
+            throw new RuntimeException("file construction failed");
+        }
+
+        javac(new String[] {
+            testJavaPath.toString(),
+            moduleInfoJavaPath.toString(),
+        });
+
+        jar(new String[] {
+            "-c",
+            "-f", testjarPath.toString(),
+            "--module-path", jmodsPath.toString(),
+            testClassPath.toString(),
+            moduleInfoClassPath.toString(),
+        });
+
+        keytool(new String[] {
+            "-genkey",
+            "-keyalg", "RSA",
+            "-dname", "CN=John Doe, OU=JPG, O=Oracle, L=Santa Clara, ST=California, C=US",
+            "-alias", "examplekey",
+            "-storepass", "password",
+            "-keypass", "password",
+            "-keystore", "examplekeystore",
+            "-validity", "365",
+        });
+
+        jarsigner(new String[] {
+            "-keystore", "examplekeystore",
+            "-verbose", testjarPath.toString(),
+            "-storepass", "password",
+            "-keypass", "password",
+            "examplekey",
+        });
+
+        try {
+            jlink(new String[] {
+                "--module-path", modulesPath,
+                "--add-modules", "test",
+                "--output", "foo",
+            });
+        } catch (Throwable ex) {
+            System.out.println("Failed as should");
+        }
+
+        try {
+            jlink(new String[] {
+                "--module-path", modulesPath,
+                "--add-modules", "test",
+                "--ignore-signing-information",
+                "--output", "foo",
+            });
+            System.out.println("Suceeded as should");
+        } catch (Throwable ex) {
+            System.err.println("Should not have failed");
+            throw new RuntimeException(ex);
+        }
+
+        System.out.println("Done");
+    }
+}
+