Hi, Quite a few command line tools are available through java.util.spi.ToolProvider. But not so jarsigner and keytool. If that finds some common interest, I'd be glad to contribute a patch or two.
Advantages of having jarsigner and/or keytool available as a tool (though that could certainly be achieved in another way as well) would be - Explicit Stdin, Stdout, and Stderr as parameters (possible already now by forking a separate process with the overhead of running the tools in their own vm) - Easy prevention of all possible calls to System.exit upon unfavourable command line paramters (at least with jarsigner, and only when working with sun.security.tools.jarsigner.Main and not with jdk.security.jarsigner.JarSigner) - Easier to detect whether a jarsigner or keytool tool is available on the class or module path or not. - Those who are more familiar with the command line options can use them also programmatically. An example seems to be JLinkSigningTest.java, [1]. Even though only a test and in that context not actually severe but still not such a bad example. I figure the following should not happen in real code: - The tests catches an Exception which is never thrown (especially not for the reason in the message, "jarsigner not found", I'd expect at best a NoClassDefFoundError but which would not be caught there). - Jarsigner (sun.security.tools.jarsigner.Main) will call System.exit instead of throwing an exception (8 occurrences of System.exit in [2]) for certain command line args - If command line arguments would be specified such that jarsigner would prompt for input, it would be most probably stuck indefinitely. I tried to search existing resources about the topic but found almost nothing, potentially related [3], [4], and [5]. I'm not entirely sure, if java.util.spi.ToolProvider is preferred over javax.tools.ToolProvider (or java.util.ServiceLoader). [7] might be a hint that java.util.spi.ToolProvider is more current than javax.tools.ToolProvider. An additional point for keytool and jarsigner as opposed to jar, javac, and other tools is that both jarsigner and keytool can use stdin when prompting for passwords or maybe X.509 details which is not currently possible through the api of java.util.spi.ToolProvider and would require an additional parameter for Stdin such as in [6]. I would propose that when jarsigner or keytool are invoked without a Stdin that they throw an exception or return a non-zero exit code as their return value immediately whenever they would have otherwise waited for input. Some system properties also have influence on the operation of jarsigner and possibly keytool (for example user.language and java.security.properties among probably others) which could not necessarily be set individually for each invocation of a tool gotten by a ToolProvider but probably that would not be necessary in many cases anyway. Just like [3] sounds according to its subject, jdk.security.jarsigner.JarSigner already has a useful API, so the need for a ToolProvider is probably not that big. The current jdk.security.jarsigner.JarSigner API, however, expects a key or certificate or chain of certificates to be provided through the API and therefore does not leverage the jarsigner and keytool command line tools' keystore accessing features and aliases. I also don't see how it would provide a possibility to verify a signed JAR but that could as well be achieved in another way than with a ToolProvider. It might be a bit of a challenge to catch all System.out and System.err such as in sun.security.tools.KeyStoreUtil.getPassWithModifier or sun.security.util.Debug or from whatever provider classes involved with algorithms loaded with "-providerClass" or "-altsigner" arguments. One conceivable approach might be the one used in the compiler with a context object holding references to Stdout and Stderr. What I don't like for now in the attached patch is that wrapping System.out or System.err PrintStreams into PrintWriters is repeated a few times and I wonder if that could be refactored or recombined into another PrintWriter constructor or PrintStream.toPrintWriter or something... So far I haven't found an overview over all the tools available through java.util.spi.ToolProvider other than the example in ToolProvider#name or other documentation that might have to be updated with introducing jarsigner as a tool. Also I wonder if Stdin/System.in should be added to the ToolProvider interface or a run method with only args that takes Stdin, Stdout, and Stderr from System.in, System.out, and System.err but I guess that can be discussed independently. Attached is a patch with JarSignerToolProvider that provides jarsigner as a tool. It's not yet meant as ready to merge but shows that it is possible and more or less how big the change is. I'm not really familiar at all with the processes involved with such a proposal and would appreciate greatly any kind of guidance. Was this mailing list the right place to propose it? Regards, Philipp [1] http://hg.openjdk.org/jdk/jdk/file/9c3fe09f69bc/test/jdk/tools/jlin k/JLinkSigningTest.java#l79 [2] http://hg.openjdk.org/jdk/jdk/file/9c3fe09f69bc/src/jdk.jartool/sha re/classes/sun/security/tools/jarsigner/Main.java [3] https://bugs.openjdk.java.net/browse/JDK-8056174 [4] https://bugs.openjdk.java.net/browse/JDK-8058778 [5] http://mail.openjdk.java.net/pipermail/security-dev/2015-March/0118 31.html [6] http://hg.openjdk.org/jdk/jdk/file/9c3fe09f69bc/src/jdk.compiler/sh are/classes/com/sun/tools/javac/api/JavacTool.java#l203 [7] http://hg.openjdk.org/jdk/jdk/file/9c3fe09f69bc/src/java.compiler/s hare/classes/javax/tools/ToolProvider.java#l90
diff -r 072b382347db src/java.base/share/classes/sun/security/tools/KeyStoreUtil.java --- a/src/java.base/share/classes/sun/security/tools/KeyStoreUtil.java Sun Feb 24 16:10:52 2019 -0500 +++ b/src/java.base/share/classes/sun/security/tools/KeyStoreUtil.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2019, 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 @@ -25,13 +25,12 @@ package sun.security.tools; - import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; - +import java.io.PrintWriter; import java.io.StreamTokenizer; import java.io.StringReader; import java.net.URL; @@ -134,12 +133,22 @@ public static char[] getPassWithModifier(String modifier, String arg, java.util.ResourceBundle rb) { + PrintWriter errWriter = new PrintWriter(System.err); + try { + return getPassWithModifier(modifier, arg, rb, errWriter); + } finally { + errWriter.flush(); + } + } + + public static char[] getPassWithModifier(String modifier, String arg, + java.util.ResourceBundle rb, PrintWriter err) { if (modifier == null) { return arg.toCharArray(); } else if (collator.compare(modifier, "env") == 0) { String value = System.getenv(arg); if (value == null) { - System.err.println(rb.getString( + err.println(rb.getString( "Cannot.find.environment.variable.") + arg); return null; } else { @@ -155,8 +164,7 @@ if (f.exists()) { url = f.toURI().toURL(); } else { - System.err.println(rb.getString( - "Cannot.find.file.") + arg); + err.println(rb.getString("Cannot.find.file.") + arg); return null; } } @@ -173,12 +181,11 @@ return value.toCharArray(); } } catch (IOException ioe) { - System.err.println(ioe); + err.println(ioe); return null; } } else { - System.err.println(rb.getString("Unknown.password.type.") + - modifier); + err.println(rb.getString("Unknown.password.type.") + modifier); return null; } } diff -r 072b382347db src/jdk.jartool/share/classes/module-info.java --- a/src/jdk.jartool/share/classes/module-info.java Sun Feb 24 16:10:52 2019 -0500 +++ b/src/jdk.jartool/share/classes/module-info.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -30,11 +30,12 @@ * This module also defines APIs for signing JAR files. * * <p> This module provides the equivalent of command-line access to - * <em>jar</em> via the {@link java.util.spi.ToolProvider ToolProvider} SPI. + * <em>jar</em> and <em>jarsigner</em> tools via the + * {@link java.util.spi.ToolProvider ToolProvider} SPI. * Instances of the tool can be obtained by calling * {@link java.util.spi.ToolProvider#findFirst ToolProvider.findFirst} * or the {@linkplain java.util.ServiceLoader service loader} with the name - * {@code "jar"}. + * {@code "jar"} or {@code "jarsigner"}. * * <dl style="font-family:'DejaVu Sans', Arial, Helvetica, sans serif"> * <dt class="simpleTagLabel">Tool Guides: @@ -50,5 +51,6 @@ exports jdk.security.jarsigner; provides java.util.spi.ToolProvider with - sun.tools.jar.JarToolProvider; + sun.tools.jar.JarToolProvider, + sun.security.tools.jarsigner.JarSignerToolProvider; } diff -r 072b382347db src/jdk.jartool/share/classes/sun/security/tools/jarsigner/JarSignerToolProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/JarSignerToolProvider.java Wed Feb 27 08:14:10 2019 +0100 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.security.tools.jarsigner; + +import java.io.PrintWriter; +import java.util.Objects; +import java.util.spi.ToolProvider; + +public class JarSignerToolProvider implements ToolProvider { + public String name() { + return "jarsigner"; + } + + public int run(PrintWriter out, PrintWriter err, String... args) { + Objects.requireNonNull(out); + Objects.requireNonNull(err); + Objects.requireNonNull(args); + for (String arg : args) { + Objects.requireNonNull(arg); + } + + return new Main(out, err).run(args); + } +} diff -r 072b382347db src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java Sun Feb 24 16:10:52 2019 -0500 +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2019, 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 @@ -111,11 +111,32 @@ // signer is not in alias list static final int SIGNED_BY_ALIAS = 0x08; // signer is in alias list - // Attention: - // This is the entry that get launched by the security tool jarsigner. + final InputStream in; + final PrintWriter out, err; + + public Main(InputStream in, PrintWriter out, PrintWriter err) { + this.in = in; + this.out = out; + this.err = err; + } + + // This is used by the jarsigner ToolProvider. + public Main(PrintWriter out, PrintWriter err) { + this(null, out, err); + } + + // This is the entry that gets launched by the jarsigner command line tool. public static void main(String args[]) throws Exception { - Main js = new Main(); - js.run(args); + int exitCode = 0; + PrintWriter outWriter = new PrintWriter(System.out); + PrintWriter errWriter = new PrintWriter(System.err); + try { + exitCode = new Main(System.in, outWriter, errWriter).run(args); + } finally { + outWriter.flush(); + errWriter.flush(); + } + if (exitCode != 0) System.exit(exitCode); } X509Certificate[] certChain; // signer's cert chain (when composing) @@ -204,7 +225,9 @@ PKIXBuilderParameters pkixParameters; Set<X509Certificate> trustedCerts = new HashSet<>(); - public void run(String args[]) { + // This is the entry that get launched by the jarsigner ToolProvider tool + // and indirectly through main by the command line jarsigner tool. + public int run(String args[]) { try { args = parseArgs(args); @@ -215,7 +238,7 @@ KeyStoreUtil.loadProviderByName(provName, providerArgs.get(provName)); if (debug) { - System.out.println("loadProviderByName: " + provName); + out.println("loadProviderByName: " + provName); } } catch (IllegalArgumentException e) { throw new Exception(String.format(rb.getString( @@ -231,7 +254,7 @@ KeyStoreUtil.loadProviderByClass(provClass, providerArgs.get(provClass), cl); if (debug) { - System.out.println("loadProviderByClass: " + provClass); + out.println("loadProviderByClass: " + provClass); } } catch (ClassCastException cce) { throw new Exception(String.format(rb.getString( @@ -248,12 +271,7 @@ loadKeyStore(keystore, false); } catch (Exception e) { if ((keystore != null) || (storepass != null)) { - System.out.println(rb.getString("jarsigner.error.") + - e.getMessage()); - if (debug) { - e.printStackTrace(); - } - System.exit(1); + throw e; } } /* if (debug) { @@ -261,19 +279,32 @@ ManifestEntryVerifier.setDebug(true); } */ - verifyJar(jarfile); + int exitCode = verifyJar(jarfile); + if (exitCode != 0) { + return exitCode; + } } else { loadKeyStore(keystore, true); getAliasInfo(alias); signJar(jarfile, alias); } + } catch (FullUsageException e) { + return e.fullusage(); + } catch (InvalidUsageException e) { + return e.usage(); + } catch (JarSignerErrorException e) { + out.println(rb.getString("jarsigner.") + e.getMessage()); + if (debug && e.getCause() != null) { + e.getCause().printStackTrace(err); + } + return 1; } catch (Exception e) { - System.out.println(rb.getString("jarsigner.error.") + e); + out.println(rb.getString("jarsigner.error.") + e); if (debug) { - e.printStackTrace(); + e.printStackTrace(err); } - System.exit(1); + return 1; } finally { // zero-out private key password if (keypass != null) { @@ -287,8 +318,8 @@ } } + int exitCode = 0; if (strict) { - int exitCode = 0; if (weakAlg != 0 || chainNotValidated || hasExpiredCert || hasExpiredTsaCert || notYetValidCert || signerSelfSigned) { exitCode |= 4; @@ -305,10 +336,8 @@ if (tsaChainNotValidated) { exitCode |= 64; } - if (exitCode != 0) { - System.exit(exitCode); - } } + return exitCode; } /* @@ -318,7 +347,7 @@ /* parse flags */ int n = 0; - if (args.length == 0) fullusage(); + if (args.length == 0) throw new FullUsageException(); String confFile = null; String command = "-sign"; @@ -343,8 +372,7 @@ if (debug) { // No need to localize debug output - System.out.println("Command line args: " + - Arrays.toString(args)); + out.println("Command line args: " + Arrays.toString(args)); } for (n=0; n < args.length; n++) { @@ -443,14 +471,12 @@ } else if (collator.compare(flags, "-altsigner") ==0) { if (++n == args.length) usageNoArg(); altSignerClass = args[n]; - System.err.println( - rb.getString("This.option.is.deprecated") + + err.println(rb.getString("This.option.is.deprecated") + "-altsigner"); } else if (collator.compare(flags, "-altsignerpath") ==0) { if (++n == args.length) usageNoArg(); altSignerClasspath = args[n]; - System.err.println( - rb.getString("This.option.is.deprecated") + + err.println(rb.getString("This.option.is.deprecated") + "-altsignerpath"); } else if (collator.compare(flags, "-sectionsonly") ==0) { signManifest = false; @@ -475,11 +501,10 @@ collator.compare(flags, "--help") == 0 || // -help: legacy. collator.compare(flags, "-help") == 0) { - fullusage(); + throw new FullUsageException(); } else { - System.err.println( - rb.getString("Illegal.option.") + flags); - usage(); + err.println(rb.getString("Illegal.option.") + flags); + throw new InvalidUsageException(); } } @@ -487,16 +512,16 @@ if (verbose == null) showcerts = false; if (jarfile == null) { - System.err.println(rb.getString("Please.specify.jarfile.name")); - usage(); + err.println(rb.getString("Please.specify.jarfile.name")); + throw new InvalidUsageException(); } if (!verify && alias == null) { - System.err.println(rb.getString("Please.specify.alias.name")); - usage(); + err.println(rb.getString("Please.specify.alias.name")); + throw new InvalidUsageException(); } if (!verify && ckaliases.size() > 1) { - System.err.println(rb.getString("Only.one.alias.can.be.specified")); - usage(); + err.println(rb.getString("Only.one.alias.can.be.specified")); + throw new InvalidUsageException(); } if (storetype == null) { @@ -527,161 +552,172 @@ } if (token && !nullStream) { - System.err.println(MessageFormat.format(rb.getString + err.println(MessageFormat.format(rb.getString (".keystore.must.be.NONE.if.storetype.is.{0}"), storetype)); - usage(); + throw new InvalidUsageException(); } if (token && keypass != null) { - System.err.println(MessageFormat.format(rb.getString + err.println(MessageFormat.format(rb.getString (".keypass.can.not.be.specified.if.storetype.is.{0}"), storetype)); - usage(); + throw new InvalidUsageException(); } if (protectedPath) { if (storepass != null || keypass != null) { - System.err.println(rb.getString + err.println(rb.getString ("If.protected.is.specified.then.storepass.and.keypass.must.not.be.specified")); - usage(); + throw new InvalidUsageException(); } } if (KeyStoreUtil.isWindowsKeyStore(storetype)) { if (storepass != null || keypass != null) { - System.err.println(rb.getString + err.println(rb.getString ("If.keystore.is.not.password.protected.then.storepass.and.keypass.must.not.be.specified")); - usage(); + throw new InvalidUsageException(); } } return args; } - static char[] getPass(String modifier, String arg) { - char[] output = KeyStoreUtil.getPassWithModifier(modifier, arg, rb); + char[] getPass(String modifier, String arg) throws InvalidUsageException { + char[] output = KeyStoreUtil.getPassWithModifier(modifier, arg, rb, err); if (output != null) return output; - usage(); - return null; // Useless, usage() already exit - } - - static void usageNoArg() { - System.out.println(rb.getString("Option.lacks.argument")); - usage(); - } - - static void usage() { - System.out.println(); - System.out.println(rb.getString("Please.type.jarsigner.help.for.usage")); - System.exit(1); + throw new InvalidUsageException(); } - static void fullusage() { - System.out.println(rb.getString - ("Usage.jarsigner.options.jar.file.alias")); - System.out.println(rb.getString - (".jarsigner.verify.options.jar.file.alias.")); - System.out.println(); - System.out.println(rb.getString - (".keystore.url.keystore.location")); - System.out.println(); - System.out.println(rb.getString - (".storepass.password.password.for.keystore.integrity")); - System.out.println(); - System.out.println(rb.getString - (".storetype.type.keystore.type")); - System.out.println(); - System.out.println(rb.getString - (".keypass.password.password.for.private.key.if.different.")); - System.out.println(); - System.out.println(rb.getString - (".certchain.file.name.of.alternative.certchain.file")); - System.out.println(); - System.out.println(rb.getString - (".sigfile.file.name.of.SF.DSA.file")); - System.out.println(); - System.out.println(rb.getString - (".signedjar.file.name.of.signed.JAR.file")); - System.out.println(); - System.out.println(rb.getString - (".digestalg.algorithm.name.of.digest.algorithm")); - System.out.println(); - System.out.println(rb.getString - (".sigalg.algorithm.name.of.signature.algorithm")); - System.out.println(); - System.out.println(rb.getString - (".verify.verify.a.signed.JAR.file")); - System.out.println(); - System.out.println(rb.getString - (".verbose.suboptions.verbose.output.when.signing.verifying.")); - System.out.println(rb.getString - (".suboptions.can.be.all.grouped.or.summary")); - System.out.println(); - System.out.println(rb.getString - (".certs.display.certificates.when.verbose.and.verifying")); - System.out.println(); - System.out.println(rb.getString - (".tsa.url.location.of.the.Timestamping.Authority")); - System.out.println(); - System.out.println(rb.getString - (".tsacert.alias.public.key.certificate.for.Timestamping.Authority")); - System.out.println(); - System.out.println(rb.getString - (".tsapolicyid.tsapolicyid.for.Timestamping.Authority")); - System.out.println(); - System.out.println(rb.getString - (".tsadigestalg.algorithm.of.digest.data.in.timestamping.request")); - System.out.println(); - System.out.println(rb.getString - (".altsigner.class.class.name.of.an.alternative.signing.mechanism")); - System.out.println(); - System.out.println(rb.getString - (".altsignerpath.pathlist.location.of.an.alternative.signing.mechanism")); - System.out.println(); - System.out.println(rb.getString - (".internalsf.include.the.SF.file.inside.the.signature.block")); - System.out.println(); - System.out.println(rb.getString - (".sectionsonly.don.t.compute.hash.of.entire.manifest")); - System.out.println(); - System.out.println(rb.getString - (".protected.keystore.has.protected.authentication.path")); - System.out.println(); - System.out.println(rb.getString - (".providerName.name.provider.name")); - System.out.println(); - System.out.println(rb.getString - (".add.provider.option")); - System.out.println(rb.getString - (".providerArg.option.1")); - System.out.println(); - System.out.println(rb.getString - (".providerClass.option")); - System.out.println(rb.getString - (".providerArg.option.2")); - System.out.println(); - System.out.println(rb.getString - (".strict.treat.warnings.as.errors")); - System.out.println(); - System.out.println(rb.getString - (".conf.url.specify.a.pre.configured.options.file")); - System.out.println(); - System.out.println(rb.getString - (".print.this.help.message")); - System.out.println(); - - System.exit(0); + void usageNoArg() throws InvalidUsageException { + out.println(rb.getString("Option.lacks.argument")); + throw new InvalidUsageException(); } - void verifyJar(String jarName) - throws Exception - { + /** + * Indicates that {@code jarsigner} should print the usage and then exit + * and is used to jump to the {@link #main main method}'s catch clause from + * where it will return with an exit code. + */ + private class InvalidUsageException extends Exception { + static final long serialVersionUID = 7112441295109570400L; + + int usage() { + out.println(); + out.println(rb.getString("Please.type.jarsigner.help.for.usage")); + return 1; + } + } + + /** + * Indicates that {@code jarsigner} should print the full usage and then + * exit. + */ + private class FullUsageException extends Exception { + static final long serialVersionUID = 8965543537622373650L; + + int fullusage() { + out.println(rb.getString + ("Usage.jarsigner.options.jar.file.alias")); + out.println(rb.getString + (".jarsigner.verify.options.jar.file.alias.")); + out.println(); + out.println(rb.getString + (".keystore.url.keystore.location")); + out.println(); + out.println(rb.getString + (".storepass.password.password.for.keystore.integrity")); + out.println(); + out.println(rb.getString + (".storetype.type.keystore.type")); + out.println(); + out.println(rb.getString + (".keypass.password.password.for.private.key.if.different.")); + out.println(); + out.println(rb.getString + (".certchain.file.name.of.alternative.certchain.file")); + out.println(); + out.println(rb.getString + (".sigfile.file.name.of.SF.DSA.file")); + out.println(); + out.println(rb.getString + (".signedjar.file.name.of.signed.JAR.file")); + out.println(); + out.println(rb.getString + (".digestalg.algorithm.name.of.digest.algorithm")); + out.println(); + out.println(rb.getString + (".sigalg.algorithm.name.of.signature.algorithm")); + out.println(); + out.println(rb.getString + (".verify.verify.a.signed.JAR.file")); + out.println(); + out.println(rb.getString + (".verbose.suboptions.verbose.output.when.signing.verifying.")); + out.println(rb.getString + (".suboptions.can.be.all.grouped.or.summary")); + out.println(); + out.println(rb.getString + (".certs.display.certificates.when.verbose.and.verifying")); + out.println(); + out.println(rb.getString + (".tsa.url.location.of.the.Timestamping.Authority")); + out.println(); + out.println(rb.getString + (".tsacert.alias.public.key.certificate.for.Timestamping.Authority")); + out.println(); + out.println(rb.getString + (".tsapolicyid.tsapolicyid.for.Timestamping.Authority")); + out.println(); + out.println(rb.getString + (".tsadigestalg.algorithm.of.digest.data.in.timestamping.request")); + out.println(); + out.println(rb.getString + (".altsigner.class.class.name.of.an.alternative.signing.mechanism")); + out.println(); + out.println(rb.getString + (".altsignerpath.pathlist.location.of.an.alternative.signing.mechanism")); + out.println(); + out.println(rb.getString + (".internalsf.include.the.SF.file.inside.the.signature.block")); + out.println(); + out.println(rb.getString + (".sectionsonly.don.t.compute.hash.of.entire.manifest")); + out.println(); + out.println(rb.getString + (".protected.keystore.has.protected.authentication.path")); + out.println(); + out.println(rb.getString + (".providerName.name.provider.name")); + out.println(); + out.println(rb.getString + (".add.provider.option")); + out.println(rb.getString + (".providerArg.option.1")); + out.println(); + out.println(rb.getString + (".providerClass.option")); + out.println(rb.getString + (".providerArg.option.2")); + out.println(); + out.println(rb.getString + (".strict.treat.warnings.as.errors")); + out.println(); + out.println(rb.getString + (".conf.url.specify.a.pre.configured.options.file")); + out.println(); + out.println(rb.getString + (".print.this.help.message")); + out.println(); + return 0; + } + } + + int verifyJar(String jarName) throws Exception { boolean anySigned = false; // if there exists entry inside jar signed - JarFile jf = null; Map<String,String> digestMap = new HashMap<>(); Map<String,PKCS7> sigMap = new HashMap<>(); Map<String,String> sigNameMap = new HashMap<>(); Map<String,String> unparsableSignatures = new HashMap<>(); - try { - jf = new JarFile(jarName, true); + try (JarFile jf = new JarFile(jarName, true)) { Vector<JarEntry> entriesVec = new Vector<>(); byte[] buffer = new byte[8192]; @@ -740,7 +776,7 @@ Map<String,List<String>> output = new LinkedHashMap<>(); if (man != null) { - if (verbose != null) System.out.println(); + if (verbose != null) out.println(); Enumeration<JarEntry> e = entriesVec.elements(); String tab = rb.getString("6SPACE"); @@ -852,42 +888,42 @@ int pipe = key.indexOf('|'); if (verbose.equals("all")) { for (String f: files) { - System.out.println(key.substring(0, pipe) + f); - System.out.printf(key.substring(pipe+1)); + out.println(key.substring(0, pipe) + f); + out.printf(key.substring(pipe + 1)); } } else { if (verbose.equals("grouped")) { for (String f: files) { - System.out.println(key.substring(0, pipe) + f); + out.println(key.substring(0, pipe) + f); } } else if (verbose.equals("summary")) { - System.out.print(key.substring(0, pipe)); + out.print(key.substring(0, pipe)); if (files.size() > 1) { - System.out.println(files.get(0) + " " + + out.println(files.get(0) + " " + String.format(rb.getString( ".and.d.more."), files.size()-1)); } else { - System.out.println(files.get(0)); + out.println(files.get(0)); } } - System.out.printf(key.substring(pipe+1)); + out.printf(key.substring(pipe + 1)); } } - System.out.println(); - System.out.println(rb.getString( + out.println(); + out.println(rb.getString( ".s.signature.was.verified.")); - System.out.println(rb.getString( + out.println(rb.getString( ".m.entry.is.listed.in.manifest")); - System.out.println(rb.getString( + out.println(rb.getString( ".k.at.least.one.certificate.was.found.in.keystore")); if (ckaliases.size() > 0) { - System.out.println(rb.getString( + out.println(rb.getString( ".X.not.signed.by.specified.alias.es.")); } } if (man == null) { - System.out.println(); - System.out.println(rb.getString("no.manifest.")); + out.println(); + out.println(rb.getString("no.manifest.")); } // If signer is a trusted cert or private entry in user's own @@ -903,7 +939,7 @@ || !sigMap.isEmpty() || !unparsableSignatures.isEmpty()) { if (verbose != null) { - System.out.println(); + out.println(); } for (String s : sigMap.keySet()) { if (!digestMap.containsKey(s)) { @@ -966,7 +1002,7 @@ sigNameMap.get(s)); } if (verbose != null) { - System.out.println(history); + out.println(history); } } else { unparsableSignatures.putIfAbsent(s, String.format( @@ -975,45 +1011,37 @@ } if (verbose != null) { for (String s : unparsableSignatures.keySet()) { - System.out.println(unparsableSignatures.get(s)); + out.println(unparsableSignatures.get(s)); } } } - System.out.println(); + out.println(); if (!anySigned) { if (seeWeak) { if (verbose != null) { - System.out.println(rb.getString("jar.treated.unsigned.see.weak.verbose")); - System.out.println("\n " + + out.println(rb.getString("jar.treated.unsigned.see.weak.verbose")); + out.println("\n " + DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS + "=" + Security.getProperty(DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS)); } else { - System.out.println(rb.getString("jar.treated.unsigned.see.weak")); + out.println(rb.getString("jar.treated.unsigned.see.weak")); } } else if (hasSignature) { - System.out.println(rb.getString("jar.treated.unsigned")); + out.println(rb.getString("jar.treated.unsigned")); } else { - System.out.println(rb.getString("jar.is.unsigned")); + out.println(rb.getString("jar.is.unsigned")); } } else { displayMessagesAndResult(false); } - return; + return 0; } catch (Exception e) { - System.out.println(rb.getString("jarsigner.") + e); - if (debug) { - e.printStackTrace(); - } - } finally { // close the resource - if (jf != null) { - jf.close(); - } + throw error(rb.getString("jarsigner.") + e, e); } - - System.exit(1); } - private void displayMessagesAndResult(boolean isSigning) { + private void displayMessagesAndResult(boolean isSigning) + throws JarSignerErrorException { String result; List<String> errors = new ArrayList<>(); List<String> warnings = new ArrayList<>(); @@ -1181,30 +1209,30 @@ } } - System.out.println(result); + out.println(result); if (strict) { if (!errors.isEmpty()) { - System.out.println(); - System.out.println(rb.getString("Error.")); - errors.forEach(System.out::println); + out.println(); + out.println(rb.getString("Error.")); + errors.forEach(out::println); } if (!warnings.isEmpty()) { - System.out.println(); - System.out.println(rb.getString("Warning.")); - warnings.forEach(System.out::println); + out.println(); + out.println(rb.getString("Warning.")); + warnings.forEach(out::println); } } else { if (!errors.isEmpty() || !warnings.isEmpty()) { - System.out.println(); - System.out.println(rb.getString("Warning.")); - errors.forEach(System.out::println); - warnings.forEach(System.out::println); + out.println(); + out.println(rb.getString("Warning.")); + errors.forEach(out::println); + warnings.forEach(out::println); } } if (!isSigning && (!errors.isEmpty() || !warnings.isEmpty())) { if (! (verbose != null && showcerts)) { - System.out.println(); - System.out.println(rb.getString( + out.println(); + out.println(rb.getString( "Re.run.with.the.verbose.and.certs.options.for.more.details.")); } } @@ -1232,8 +1260,8 @@ } if (!info.isEmpty()) { - System.out.println(); - info.forEach(System.out::println); + out.println(); + info.forEach(out::println); } } @@ -1553,13 +1581,13 @@ builder.eventHandler((action, file) -> { switch (action) { case "signing": - System.out.println(rb.getString(".signing.") + file); + out.println(rb.getString(".signing.") + file); break; case "adding": - System.out.println(rb.getString(".adding.") + file); + out.println(rb.getString(".adding.") + file); break; case "updating": - System.out.println(rb.getString(".updating.") + file); + out.println(rb.getString(".updating.") + file); break; default: throw new IllegalArgumentException("unknown action: " @@ -1586,12 +1614,11 @@ if (tsaURI != null) { if (verbose != null) { - System.out.println( - rb.getString("requesting.a.signature.timestamp")); + out.println(rb.getString("requesting.a.signature.timestamp")); if (tsaUrl != null) { - System.out.println(rb.getString("TSA.location.") + tsaUrl); + out.println(rb.getString("TSA.location.") + tsaUrl); } else if (tsaCert != null) { - System.out.println(rb.getString("TSA.certificate.") + + out.println(rb.getString("TSA.certificate.") + printCert(true, "", tsaCert, null, false)); } } @@ -1608,8 +1635,8 @@ if (altSignerClass != null) { builder.setProperty("altSigner", altSignerClass); if (verbose != null) { - System.out.println( - rb.getString("using.an.alternative.signing.mechanism")); + out.println(rb.getString + ("using.an.alternative.signing.mechanism")); } } @@ -1675,7 +1702,7 @@ } if (verbose != null) { - System.out.println(); + out.println(); } // The JarSigner API always accepts the timestamp received. @@ -1698,11 +1725,11 @@ // Spaces before the ">>> Signer" and other lines are different String result = certsAndTSInfo("", " ", Arrays.asList(certChain), ts); if (verbose != null) { - System.out.println(result); + out.println(result); } } catch (Exception e) { if (debug) { - e.printStackTrace(); + e.printStackTrace(err); } } @@ -1836,7 +1863,8 @@ return sb.toString(); } - void loadKeyStore(String keyStoreName, boolean prompt) { + void loadKeyStore(String keyStoreName, boolean prompt) + throws JarSignerErrorException { if (!nullStream && keyStoreName == null) { keyStoreName = System.getProperty("user.home") + File.separator @@ -1947,7 +1975,7 @@ } } - X509Certificate getTsaCert(String alias) { + X509Certificate getTsaCert(String alias) throws JarSignerErrorException { java.security.cert.Certificate cs = null; @@ -2118,18 +2146,31 @@ } } - void error(String message) { - System.out.println(rb.getString("jarsigner.")+message); - System.exit(1); + /** + * Indicates that {@code jarsigner} cannot continue and terminate normally + * and is used to jump to the {@link #main main method}'s catch clause from + * where it will return with an exit code. + */ + static class JarSignerErrorException extends Exception { + static final long serialVersionUID = -6575209030723808865L; + + JarSignerErrorException(String message) { + super(message); + } + + JarSignerErrorException(String message, Throwable cause) { + super(message, cause); + } } + JarSignerErrorException error(String message) + throws JarSignerErrorException { + throw new JarSignerErrorException(message); + } - void error(String message, Throwable e) { - System.out.println(rb.getString("jarsigner.")+message); - if (debug) { - e.printStackTrace(); - } - System.exit(1); + JarSignerErrorException error(String message, Throwable e) + throws JarSignerErrorException { + throw new JarSignerErrorException(message, e); } /** @@ -2148,7 +2189,7 @@ null, parameter); } catch (Exception e) { if (debug) { - e.printStackTrace(); + e.printStackTrace(err); } // Exception might be dismissed if another warning flag @@ -2198,21 +2239,24 @@ } } - char[] getPass(String prompt) { - System.err.print(prompt); - System.err.flush(); + char[] getPass(String prompt) throws JarSignerErrorException { + if (in == null) { + throw error(rb.getString("unable.to.read.password.")); + } + + err.print(prompt); + err.flush(); try { - char[] pass = Password.readPassword(System.in); + char[] pass = Password.readPassword(in); if (pass == null) { - error(rb.getString("you.must.enter.key.password")); + throw error(rb.getString("you.must.enter.key.password")); } else { return pass; } } catch (IOException ioe) { - error(rb.getString("unable.to.read.password.")+ioe.getMessage()); + throw error(rb.getString("unable.to.read.password.") + + ioe.getMessage()); } - // this shouldn't happen - return null; } } diff -r 072b382347db test/jdk/java/util/jar/JarInputStream/ExtraFileInMetaInf.java --- a/test/jdk/java/util/jar/JarInputStream/ExtraFileInMetaInf.java Sun Feb 24 16:10:52 2019 -0500 +++ b/test/jdk/java/util/jar/JarInputStream/ExtraFileInMetaInf.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -30,6 +30,7 @@ */ import java.util.jar.*; +import java.util.spi.ToolProvider; import java.io.*; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -51,9 +52,10 @@ new File("ks").delete(); sun.security.tools.keytool.Main.main( ("-keystore ks -storepass changeit -keypass changeit " + - "-keyalg rsa -alias a -dname CN=A -genkeypair").split(" ")); - sun.security.tools.jarsigner.Main.main( - "-keystore ks -storepass changeit x.jar a".split(" ")); + "-keyalg rsa -alias a -dname CN=A -genkeypair").split(" ")); + ToolProvider jarsignertool = ToolProvider.findFirst("jarsigner").get(); + jarsignertool.run(System.out, System.err, + "-keystore ks -storepass changeit x.jar a".split(" ")); // Check if the entries are signed try (JarInputStream jis = diff -r 072b382347db test/jdk/sun/security/tools/jarsigner/EntriesOrder.java --- a/test/jdk/sun/security/tools/jarsigner/EntriesOrder.java Sun Feb 24 16:10:52 2019 -0500 +++ b/test/jdk/sun/security/tools/jarsigner/EntriesOrder.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -40,6 +40,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarInputStream; +import java.util.spi.ToolProvider; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -69,18 +70,18 @@ Files.write(Paths.get("META-INF/inf"), "inf".getBytes()); // Pack, sign, and extract to get all files - sun.tools.jar.Main m = - new sun.tools.jar.Main(System.out, System.err, "jar"); - if (!m.run("cvf a.jar a META-INF/inf".split(" "))) { + ToolProvider jartool = ToolProvider.findFirst("jar").get(); + if (jartool.run(System.out, System.err, + "cvf a.jar a META-INF/inf".split(" ")) != 0) { throw new Exception("jar creation failed"); } sun.security.tools.keytool.Main.main( ("-keystore jks -storepass changeit -keypass changeit -dname" + " CN=A -alias a -genkeypair -keyalg rsa").split(" ")); - sun.security.tools.jarsigner.Main.main( - "-keystore jks -storepass changeit a.jar a".split(" ")); - m = new sun.tools.jar.Main(System.out, System.err, "jar"); - if (!m.run("xvf a.jar".split(" "))) { + ToolProvider jarsignertool = ToolProvider.findFirst("jarsigner").get(); + jarsignertool.run(System.out, System.err, + "-keystore jks -storepass changeit a.jar a".split(" ")); + if (jartool.run(System.out, System.err, "xvf a.jar".split(" ")) != 0) { throw new Exception("jar extraction failed"); } diff -r 072b382347db test/jdk/sun/security/tools/jarsigner/JarSigningNonAscii.java --- a/test/jdk/sun/security/tools/jarsigner/JarSigningNonAscii.java Sun Feb 24 16:10:52 2019 -0500 +++ b/test/jdk/sun/security/tools/jarsigner/JarSigningNonAscii.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2019, 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 @@ -32,6 +32,7 @@ import java.io.*; import java.util.*; import java.util.jar.*; +import java.util.spi.ToolProvider; import java.security.cert.Certificate; public class JarSigningNonAscii { @@ -63,7 +64,8 @@ "-signedJar", signedJar, unsignedJar, "b" }; - sun.security.tools.jarsigner.Main.main(jsArgs); + ToolProvider jarsignertool = ToolProvider.findFirst("jarsigner").get(); + jarsignertool.run(System.out, System.err, jsArgs); // verify the signed jar file diff -r 072b382347db test/jdk/sun/security/tools/jarsigner/LargeJarEntry.java --- a/test/jdk/sun/security/tools/jarsigner/LargeJarEntry.java Sun Feb 24 16:10:52 2019 -0500 +++ b/test/jdk/sun/security/tools/jarsigner/LargeJarEntry.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2019, 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 @@ -35,6 +35,7 @@ import java.io.FileOutputStream; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; +import java.util.spi.ToolProvider; import java.util.zip.CRC32; public class LargeJarEntry { @@ -73,7 +74,8 @@ jarName, "b" }; // now, try to sign it try { - sun.security.tools.jarsigner.Main.main(jsArgs); + ToolProvider jarsignertool = ToolProvider.findFirst("jarsigner").get(); + jarsignertool.run(System.out, System.err, jsArgs); } catch (OutOfMemoryError err) { throw new Exception("Test failed with OutOfMemoryError", err); } finally { diff -r 072b382347db test/jdk/sun/security/tools/jarsigner/Options.java --- a/test/jdk/sun/security/tools/jarsigner/Options.java Sun Feb 24 16:10:52 2019 -0500 +++ b/test/jdk/sun/security/tools/jarsigner/Options.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -52,6 +52,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; +import java.util.spi.ToolProvider; public class Options { @@ -69,7 +70,8 @@ " CN=A -alias a -genkeypair -keyalg rsa").split(" ")); // -altsign - sun.security.tools.jarsigner.Main.main( + ToolProvider jarsignertool = ToolProvider.findFirst("jarsigner").get(); + jarsignertool.run(System.out, System.err, ("-debug -signedjar altsign.jar -keystore jks -storepass changeit" + " -altsigner Options$X a.jar a").split(" ")); @@ -83,7 +85,7 @@ } // -sigfile, -digestalg, -sigalg, -internalsf, -sectionsonly - sun.security.tools.jarsigner.Main.main( + jarsignertool.run(System.out, System.err, ("-debug -signedjar new.jar -keystore jks -storepass changeit" + " -sigfile olala -digestalg SHA1 -sigalg SHA224withRSA" + " -internalsf -sectionsonly a.jar a").split(" ")); diff -r 072b382347db test/jdk/tools/jlink/JLinkSigningTest.java --- a/test/jdk/tools/jlink/JLinkSigningTest.java Sun Feb 24 16:10:52 2019 -0500 +++ b/test/jdk/tools/jlink/JLinkSigningTest.java Wed Feb 27 08:14:10 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2019, 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 @@ -46,6 +46,8 @@ public class JLinkSigningTest { private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") .orElseThrow(() -> new RuntimeException("jar tool not found")); + private static final ToolProvider JARSIGNER_TOOL = ToolProvider.findFirst("jarsigner") + .orElseThrow(() -> new RuntimeException("jarsigner tool not found")); private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac") .orElseThrow(() -> new RuntimeException("javac tool not found")); private static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink") @@ -76,10 +78,8 @@ static void jarsigner(String[] args) { report("jarsigner", args); - try { - sun.security.tools.jarsigner.Main.main(args); - } catch (Exception ex) { - throw new RuntimeException("jarsigner not found"); + if (JARSIGNER_TOOL.run(System.out, System.err, args) != 0) { + throw new RuntimeException("jarsigner failed"); } }