# HG changeset patch # User weijun # Date 1402482430 -28800 # Node ID 89d9bd9eba969b9c9dedb4daaca3f0c08d64cdd3 # Parent c3514175b6cd1c01b925c33a6cd7f660b7d30432 8023197: Pre-configured command line options for keytool and jarsigner Reviewed-by: xuelei diff -r c3514175b6cd -r 89d9bd9eba96 jdk/src/share/classes/sun/security/tools/KeyStoreUtil.java --- a/jdk/src/share/classes/sun/security/tools/KeyStoreUtil.java Tue Jun 10 14:40:20 2014 +0200 +++ b/jdk/src/share/classes/sun/security/tools/KeyStoreUtil.java Wed Jun 11 18:27:10 2014 +0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2014, 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,19 +25,28 @@ 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.StreamTokenizer; +import java.io.StringReader; import java.net.URL; import java.security.KeyStore; import java.text.Collator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Locale; +import java.util.Properties; + +import sun.security.util.PropertyExpander; /** *

This class provides several utilities to KeyStore. @@ -151,4 +160,83 @@ return null; } } + + /** + * Parses a option line likes + * -genkaypair -dname "CN=Me" + * and add the results into a list + * @param list the list to fill into + * @param s the line + */ + private static void parseArgsLine(List list, String s) + throws IOException, PropertyExpander.ExpandException { + StreamTokenizer st = new StreamTokenizer(new StringReader(s)); + + st.resetSyntax(); + st.whitespaceChars(0x00, 0x20); + st.wordChars(0x21, 0xFF); + // Everything is a word char except for quotation and apostrophe + st.quoteChar('"'); + st.quoteChar('\''); + + while (true) { + if (st.nextToken() == StreamTokenizer.TT_EOF) { + break; + } + list.add(PropertyExpander.expand(st.sval)); + } + } + + /** + * Prepends matched options from a pre-configured options file. + * @param tool the name of the tool, can be "keytool" or "jarsigner" + * @param file the pre-configured options file + * @param c1 the name of the command, with the "-" prefix, + * must not be null + * @param c2 the alternative command name, with the "-" prefix, + * null if none. For example, "genkey" is alt name for + * "genkeypair". A command can only have one alt name now. + * @param args existing arguments + * @return arguments combined + * @throws IOException if there is a file I/O or format error + * @throws PropertyExpander.ExpandException + * if there is a property expansion error + */ + public static String[] expandArgs(String tool, String file, + String c1, String c2, String[] args) + throws IOException, PropertyExpander.ExpandException { + + List result = new ArrayList<>(); + Properties p = new Properties(); + p.load(new FileInputStream(file)); + + String s = p.getProperty(tool + ".all"); + if (s != null) { + parseArgsLine(result, s); + } + + // Cannot provide both -genkey and -genkeypair + String s1 = p.getProperty(tool + "." + c1.substring(1)); + String s2 = null; + if (c2 != null) { + s2 = p.getProperty(tool + "." + c2.substring(1)); + } + if (s1 != null && s2 != null) { + throw new IOException("Cannot have both " + c1 + " and " + + c2 + " as pre-configured options"); + } + if (s1 == null) { + s1 = s2; + } + if (s1 != null) { + parseArgsLine(result, s1); + } + + if (result.isEmpty()) { + return args; + } else { + result.addAll(Arrays.asList(args)); + return result.toArray(new String[result.size()]); + } + } } diff -r c3514175b6cd -r 89d9bd9eba96 jdk/src/share/classes/sun/security/tools/jarsigner/Main.java --- a/jdk/src/share/classes/sun/security/tools/jarsigner/Main.java Tue Jun 10 14:40:20 2014 +0200 +++ b/jdk/src/share/classes/sun/security/tools/jarsigner/Main.java Wed Jun 11 18:27:10 2014 +0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2014, 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 @@ -178,7 +178,7 @@ public void run(String args[]) { try { - parseArgs(args); + args = parseArgs(args); // Try to load and install the specified providers if (providers != null) { @@ -282,11 +282,39 @@ /* * Parse command line arguments. */ - void parseArgs(String args[]) { + String[] parseArgs(String args[]) throws Exception { /* parse flags */ int n = 0; if (args.length == 0) fullusage(); + + String confFile = null; + String command = "-sign"; + for (n=0; n < args.length; n++) { + if (collator.compare(args[n], "-verify") == 0) { + command = "-verify"; + } else if (collator.compare(args[n], "-conf") == 0) { + if (n == args.length - 1) { + usageNoArg(); + } + confFile = args[++n]; + } + } + + if (confFile != null) { + args = KeyStoreUtil.expandArgs( + "jarsigner", confFile, command, null, args); + } + + debug = Arrays.stream(args).anyMatch( + x -> collator.compare(x, "-debug") == 0); + + if (debug) { + // No need to localize debug output + System.out.println("Command line args: " + + Arrays.toString(args)); + } + for (n=0; n < args.length; n++) { String flags = args[n]; @@ -307,6 +335,8 @@ alias = flags; ckaliases.add(alias); } + } else if (collator.compare(flags, "-conf") == 0) { + if (++n == args.length) usageNoArg(); } else if (collator.compare(flags, "-keystore") == 0) { if (++n == args.length) usageNoArg(); keystore = args[n]; @@ -347,7 +377,7 @@ if (++n == args.length) usageNoArg(); tSADigestAlg = args[n]; } else if (collator.compare(flags, "-debug") ==0) { - debug = true; + // Already processed } else if (collator.compare(flags, "-keypass") ==0) { if (++n == args.length) usageNoArg(); keypass = getPass(modifier, args[n]); @@ -466,6 +496,7 @@ usage(); } } + return args; } static char[] getPass(String modifier, String arg) { @@ -568,6 +599,9 @@ 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.exit(0); } diff -r c3514175b6cd -r 89d9bd9eba96 jdk/src/share/classes/sun/security/tools/jarsigner/Resources.java --- a/jdk/src/share/classes/sun/security/tools/jarsigner/Resources.java Tue Jun 10 14:40:20 2014 +0200 +++ b/jdk/src/share/classes/sun/security/tools/jarsigner/Resources.java Wed Jun 11 18:27:10 2014 +0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2014, 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 @@ -108,6 +108,8 @@ " [-providerArg ]] ... master class file and constructor argument"}, {".strict.treat.warnings.as.errors", "[-strict] treat warnings as errors"}, + {".conf.url.specify.a.pre.configured.options.file", + "[-conf ] specify a pre-configured options file"}, {"Option.lacks.argument", "Option lacks argument"}, {"Please.type.jarsigner.help.for.usage", "Please type jarsigner -help for usage"}, {"Please.specify.jarfile.name", "Please specify jarfile name"}, diff -r c3514175b6cd -r 89d9bd9eba96 jdk/src/share/classes/sun/security/tools/keytool/Main.java --- a/jdk/src/share/classes/sun/security/tools/keytool/Main.java Tue Jun 10 14:40:20 2014 +0200 +++ b/jdk/src/share/classes/sun/security/tools/keytool/Main.java Wed Jun 11 18:27:10 2014 +0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2014, 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 @@ -38,7 +38,6 @@ import java.security.Timestamp; import java.security.UnrecoverableEntryException; import java.security.UnrecoverableKeyException; -import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.Provider; import java.security.cert.Certificate; @@ -64,6 +63,7 @@ import java.security.cert.X509CRLSelector; import javax.security.auth.x500.X500Principal; import java.util.Base64; + import sun.security.util.ObjectIdentifier; import sun.security.pkcs10.PKCS10; import sun.security.pkcs10.PKCS10Attribute; @@ -242,16 +242,44 @@ final String description; final Option[] options; + final String name; + + String altName; // "genkey" is altName for "genkeypair" + Command(String d, Option... o) { description = d; options = o; + name = "-" + name().toLowerCase(Locale.ENGLISH); } @Override public String toString() { - return "-" + name().toLowerCase(Locale.ENGLISH); + return name; + } + public String getAltName() { + return altName; + } + public void setAltName(String altName) { + this.altName = altName; + } + public static Command getCommand(String cmd) { + for (Command c: Command.values()) { + if (collator.compare(cmd, c.name) == 0 + || (c.altName != null + && collator.compare(cmd, c.altName) == 0)) { + return c; + } + } + return null; } }; + static { + Command.GENKEYPAIR.setAltName("-genkey"); + Command.IMPORTCERT.setAltName("-import"); + Command.EXPORTCERT.setAltName("-export"); + Command.IMPORTPASS.setAltName("-importpassword"); + } + enum Option { ALIAS("alias", "", "alias.name.of.the.entry.to.process"), DESTALIAS("destalias", "", "destination.alias"), @@ -335,7 +363,7 @@ private void run(String[] args, PrintStream out) throws Exception { try { - parseArgs(args); + args = parseArgs(args); if (command != null) { doCommands(out); } @@ -366,11 +394,43 @@ /** * Parse command line arguments. */ - void parseArgs(String[] args) { + String[] parseArgs(String[] args) throws Exception { int i=0; boolean help = args.length == 0; + String confFile = null; + + for (i=0; i < args.length; i++) { + String flags = args[i]; + if (flags.startsWith("-")) { + if (collator.compare(flags, "-conf") == 0) { + if (i == args.length - 1) { + errorNeedArgument(flags); + } + confFile = args[++i]; + } else { + Command c = Command.getCommand(flags); + if (c != null) command = c; + } + } + } + + if (confFile != null && command != null) { + args = KeyStoreUtil.expandArgs("keytool", confFile, + command.toString(), + command.getAltName(), args); + } + + debug = Arrays.stream(args).anyMatch( + x -> collator.compare(x, "-debug") == 0); + + if (debug) { + // No need to localize debug output + System.out.println("Command line args: " + + Arrays.toString(args)); + } + for (i=0; (i < args.length) && args[i].startsWith("-"); i++) { String flags = args[i]; @@ -395,34 +455,18 @@ modifier = flags.substring(pos+1); flags = flags.substring(0, pos); } + /* * command modes */ - boolean isCommand = false; - for (Command c: Command.values()) { - if (collator.compare(flags, c.toString()) == 0) { - command = c; - isCommand = true; - break; - } - } - - if (isCommand) { - // already recognized as a command - } else if (collator.compare(flags, "-export") == 0) { - command = EXPORTCERT; - } else if (collator.compare(flags, "-genkey") == 0) { - command = GENKEYPAIR; - } else if (collator.compare(flags, "-import") == 0) { - command = IMPORTCERT; - } else if (collator.compare(flags, "-importpassword") == 0) { - command = IMPORTPASS; - } - /* - * Help - */ - else if (collator.compare(flags, "-help") == 0) { + Command c = Command.getCommand(flags); + + if (c != null) { + command = c; + } else if (collator.compare(flags, "-help") == 0) { help = true; + } else if (collator.compare(flags, "-conf") == 0) { + i++; } /* @@ -522,7 +566,7 @@ else if (collator.compare(flags, "-v") == 0) { verbose = true; } else if (collator.compare(flags, "-debug") == 0) { - debug = true; + // Already processed } else if (collator.compare(flags, "-rfc") == 0) { rfc = true; } else if (collator.compare(flags, "-noprompt") == 0) { @@ -556,6 +600,8 @@ usage(); command = null; } + + return args; } boolean isKeyStoreRelated(Command cmd) { diff -r c3514175b6cd -r 89d9bd9eba96 jdk/src/share/classes/sun/security/tools/keytool/Resources.java --- a/jdk/src/share/classes/sun/security/tools/keytool/Resources.java Tue Jun 10 14:40:20 2014 +0200 +++ b/jdk/src/share/classes/sun/security/tools/keytool/Resources.java Wed Jun 11 18:27:10 2014 +0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2014, 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 @@ -48,7 +48,8 @@ "Key and Certificate Management Tool"}, {"Commands.", "Commands:"}, {"Use.keytool.command.name.help.for.usage.of.command.name", - "Use \"keytool -command_name -help\" for usage of command_name"}, + "Use \"keytool -command_name -help\" for usage of command_name.\n" + + "Use the -conf option to specify a pre-configured options file."}, // keytool: help: commands {"Generates.a.certificate.request", "Generates a certificate request"}, //-certreq diff -r c3514175b6cd -r 89d9bd9eba96 jdk/test/sun/security/tools/keytool/default_options.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/sun/security/tools/keytool/default_options.sh Wed Jun 11 18:27:10 2014 +0800 @@ -0,0 +1,117 @@ +# +# Copyright (c) 2014, 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 8023197 +# @summary Pre-configured command line options for keytool and jarsigner +# + +if [ "${TESTJAVA}" = "" ] ; then + JAVAC_CMD=`which javac` + TESTJAVA=`dirname $JAVAC_CMD`/.. +fi + +KS=ks +KEYTOOL="$TESTJAVA/bin/keytool ${TESTTOOLVMOPTS}" +JAR="$TESTJAVA/bin/jar ${TESTTOOLVMOPTS}" +JARSIGNER="$TESTJAVA/bin/jarsigner ${TESTTOOLVMOPTS}" + +rm $KS 2> /dev/null + +export PASS=changeit + +# keytool + +cat < kt.conf +# A Pre-configured options file +keytool.all = -storepass:env PASS -keypass:env PASS -keystore \${user.dir}/$KS -debug +keytool.genkey = -keyalg ec -ext bc +keytool.delete = -keystore nothing +EOF + +# kt.conf is read +$KEYTOOL -conf kt.conf -genkeypair -dname CN=A -alias a || exit 1 +$KEYTOOL -conf kt.conf -list -alias a -v > a_certinfo || exit 2 +grep "Signature algorithm name" a_certinfo | grep ECDSA || exit 3 +grep "BasicConstraints" a_certinfo || exit 4 + +# kt.conf is read, and dup multi-valued options processed as expected +$KEYTOOL -conf kt.conf -genkeypair -dname CN=B -alias b -ext ku=ds \ + || exit 11 +$KEYTOOL -conf kt.conf -list -alias b -v > b_certinfo || exit 12 +grep "BasicConstraints" b_certinfo || exit 14 +grep "DigitalSignature" b_certinfo || exit 15 + +# Single-valued option in command section override all +$KEYTOOL -conf kt.conf -delete -alias a && exit 16 + +# Single-valued option on command line overrides again +$KEYTOOL -conf kt.conf -delete -alias b -keystore $KS || exit 17 + +# jarsigner + +cat < js.conf +jarsigner.all = -keystore \${user.dir}/$KS -storepass:env PASS -debug -strict +jarsigner.sign = -digestalg SHA1 +jarsigner.verify = -verbose:summary + +EOF + +$JAR cvf a.jar ks js.conf kt.conf + +$JARSIGNER -conf js.conf a.jar a || exit 21 +$JARSIGNER -conf js.conf -verify a.jar > jarsigner.out || exit 22 +grep "and 2 more" jarsigner.out || exit 23 +$JAR xvf a.jar META-INF/MANIFEST.MF +grep "SHA1-Digest" META-INF/MANIFEST.MF || exit 24 + +# Error cases + +# File does not exist +$KEYTOOL -conf no-such-file -help -list && exit 31 + +# Cannot have both standard name (-genkeypair) and legacy name (-genkey) +cat < bad.conf +keytool.all = -storepass:env PASS -keypass:env PASS -keystore ks +keytool.genkeypair = -keyalg rsa +keytool.genkey = -keyalg ec +EOF + +$KEYTOOL -conf bad.conf -genkeypair -alias me -dname "cn=me" && exit 32 + +# Unknown options are rejected by tool +cat < bad.conf +keytool.all=-unknown +EOF + +$KEYTOOL -conf bad.conf -help -list && exit 33 + +# System property must be present +cat < bad.conf +keytool.all = -keystore \${no.such.prop} +EOF + +$KEYTOOL -conf bad.conf -help -list && exit 34 + +echo Done +exit 0