8023197: Pre-configured command line options for keytool and jarsigner
authorweijun
Wed, 11 Jun 2014 18:27:10 +0800
changeset 24868 89d9bd9eba96
parent 24867 c3514175b6cd
child 24869 6cdd5aa7e259
8023197: Pre-configured command line options for keytool and jarsigner Reviewed-by: xuelei
jdk/src/share/classes/sun/security/tools/KeyStoreUtil.java
jdk/src/share/classes/sun/security/tools/jarsigner/Main.java
jdk/src/share/classes/sun/security/tools/jarsigner/Resources.java
jdk/src/share/classes/sun/security/tools/keytool/Main.java
jdk/src/share/classes/sun/security/tools/keytool/Resources.java
jdk/test/sun/security/tools/keytool/default_options.sh
--- 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;
 
 /**
  * <p> This class provides several utilities to <code>KeyStore</code>.
@@ -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<String> 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<String> 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()]);
+        }
+    }
 }
--- 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);
     }
--- 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 <arg>]] ... 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 <url>]               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"},
--- 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>", "alias.name.of.the.entry.to.process"),
         DESTALIAS("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) {
--- 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 <url> option to specify a pre-configured options file."},
         // keytool: help: commands
         {"Generates.a.certificate.request",
                 "Generates a certificate request"}, //-certreq
--- /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 <<EOF > 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 <<EOF > 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 <<EOF > 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 <<EOF > bad.conf
+keytool.all=-unknown
+EOF
+
+$KEYTOOL -conf bad.conf -help -list && exit 33
+
+# System property must be present
+cat <<EOF > bad.conf
+keytool.all = -keystore \${no.such.prop}
+EOF
+
+$KEYTOOL -conf bad.conf -help -list && exit 34
+
+echo Done
+exit 0