# HG changeset patch # User jlaskey # Date 1478538642 14400 # Node ID 612b698f8b8f4df3d5b2e15834d95de4d14a135a # Parent 75bfe1e98bf3f21557fc87b6575654daadc67328 8159393: jlink should print a warning that a signed modular JAR will be treated as unsigned Reviewed-by: alanb, sundar, mullan, weijun diff -r 75bfe1e98bf3 -r 612b698f8b8f jdk/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.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); } diff -r 75bfe1e98bf3 -r 612b698f8b8f jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java --- 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 void fail(Class 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(false, (task, opt, arg) -> { task.options.fullVersion = true; - }, true, "--full-version"),}; + }, true, "--full-version"), + new Option(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 modulePath = new ArrayList<>(); - Set limitMods = new HashSet<>(); - Set addMods = new HashSet<>(); + final List modulePath = new ArrayList<>(); + final Set limitMods = new HashSet<>(); + final Set 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 checkAddMods(Set 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 addMods, Set limitMods, ByteOrder order, - Path retainModulesPath) + Path retainModulesPath, + boolean ignoreSigning) throws IOException { if (addMods.isEmpty()) { @@ -390,10 +380,10 @@ Map 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 archives; final ByteOrder order; final Path packagedModulesPath; + final boolean ignoreSigning; + final Set archives; ImageHelper(Configuration cf, Map 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 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 diff -r 75bfe1e98bf3 -r 612b698f8b8f jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java --- 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)); diff -r 75bfe1e98bf3 -r 612b698f8b8f jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties --- 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 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} diff -r 75bfe1e98bf3 -r 612b698f8b8f jdk/test/tools/jlink/JLinkSigningTest.java --- /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"); + } +} +