8159393: jlink should print a warning that a signed modular JAR will be treated as unsigned
Reviewed-by: alanb, sundar, mullan, weijun
--- 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");
+ }
+}
+