8213400: Support choosing group name in keytool keypair generation
authorweijun
Wed, 14 Nov 2018 08:46:25 +0800
changeset 52511 ddcbc20e8c6a
parent 52510 3b91496409fc
child 52512 1838347a803b
8213400: Support choosing group name in keytool keypair generation Reviewed-by: apetcher, xuelei
src/java.base/share/classes/sun/security/tools/keytool/CertAndKeyGen.java
src/java.base/share/classes/sun/security/tools/keytool/Main.java
src/java.base/share/classes/sun/security/tools/keytool/Resources.java
test/jdk/sun/security/tools/keytool/GroupName.java
--- a/src/java.base/share/classes/sun/security/tools/keytool/CertAndKeyGen.java	Tue Nov 13 16:17:24 2018 -0800
+++ b/src/java.base/share/classes/sun/security/tools/keytool/CertAndKeyGen.java	Wed Nov 14 08:46:25 2018 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2018, 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,8 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateEncodingException;
 import java.security.*;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.NamedParameterSpec;
 import java.util.Date;
 
 import sun.security.pkcs10.PKCS10;
@@ -48,8 +50,7 @@
  * parameters, such as DSS/DSA.  Some sites' Certificate Authorities
  * adopt fixed algorithm parameters, which speeds up some operations
  * including key generation and signing.  <em>At this time, this interface
- * does not provide a way to provide such algorithm parameters, e.g.
- * by providing the CA certificate which includes those parameters.</em>
+ * supports initializing with a named group.</em>
  *
  * <P>Also, note that at this time only signature-capable keys may be
  * acquired through this interface.  Diffie-Hellman keys, used for secure
@@ -77,6 +78,7 @@
     {
         keyGen = KeyPairGenerator.getInstance(keyType);
         this.sigAlg = sigAlg;
+        this.keyType = keyType;
     }
 
     /**
@@ -106,6 +108,7 @@
             }
         }
         this.sigAlg = sigAlg;
+        this.keyType = keyType;
     }
 
     /**
@@ -121,41 +124,58 @@
         prng = generator;
     }
 
+    public void generate(String name) {
+        try {
+            if (prng == null) {
+                prng = new SecureRandom();
+            }
+            try {
+                keyGen.initialize(new NamedParameterSpec(name), prng);
+            } catch (InvalidAlgorithmParameterException e) {
+                if (keyType.equalsIgnoreCase("EC")) {
+                    // EC has another NamedParameterSpec
+                    keyGen.initialize(new ECGenParameterSpec(name), prng);
+                } else {
+                    throw e;
+                }
+            }
+
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+        generateInternal();
+    }
+
     // want "public void generate (X509Certificate)" ... inherit DSA/D-H param
 
+    public void generate(int keyBits) {
+        if (keyBits != -1) {
+            try {
+                if (prng == null) {
+                    prng = new SecureRandom();
+                }
+                keyGen.initialize(keyBits, prng);
+
+            } catch (Exception e) {
+                throw new IllegalArgumentException(e.getMessage());
+            }
+        }
+        generateInternal();
+    }
+
     /**
-     * Generates a random public/private key pair, with a given key
-     * size.  Different algorithms provide different degrees of security
-     * for the same key size, because of the "work factor" involved in
-     * brute force attacks.  As computers become faster, it becomes
-     * easier to perform such attacks.  Small keys are to be avoided.
+     * Generates a random public/private key pair.
      *
-     * <P>Note that not all values of "keyBits" are valid for all
-     * algorithms, and not all public key algorithms are currently
+     * <P>Note that not all public key algorithms are currently
      * supported for use in X.509 certificates.  If the algorithm
      * you specified does not produce X.509 compatible keys, an
      * invalid key exception is thrown.
      *
-     * @param keyBits the number of bits in the keys.
-     * @exception InvalidKeyException if the environment does not
+     * @exception IllegalArgumentException if the environment does not
      *  provide X.509 public keys for this signature algorithm.
      */
-    public void generate (int keyBits)
-    throws InvalidKeyException
-    {
-        KeyPair pair;
-
-        try {
-            if (prng == null) {
-                prng = new SecureRandom();
-            }
-            keyGen.initialize(keyBits, prng);
-            pair = keyGen.generateKeyPair();
-
-        } catch (Exception e) {
-            throw new IllegalArgumentException(e.getMessage());
-        }
-
+    private void generateInternal() {
+        KeyPair pair = keyGen.generateKeyPair();
         publicKey = pair.getPublic();
         privateKey = pair.getPrivate();
 
@@ -333,6 +353,7 @@
     }
 
     private SecureRandom        prng;
+    private String              keyType;
     private String              sigAlg;
     private KeyPairGenerator    keyGen;
     private PublicKey           publicKey;
--- a/src/java.base/share/classes/sun/security/tools/keytool/Main.java	Tue Nov 13 16:17:24 2018 -0800
+++ b/src/java.base/share/classes/sun/security/tools/keytool/Main.java	Wed Nov 14 08:46:25 2018 +0800
@@ -28,13 +28,13 @@
 import java.io.*;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.security.AlgorithmParameters;
 import java.security.CodeSigner;
 import java.security.CryptoPrimitive;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.MessageDigest;
 import java.security.Key;
-import java.security.NoSuchProviderException;
 import java.security.PublicKey;
 import java.security.PrivateKey;
 import java.security.Signature;
@@ -68,6 +68,7 @@
 import javax.security.auth.x500.X500Principal;
 import java.util.Base64;
 
+import sun.security.util.ECKeySizeParameterSpec;
 import sun.security.util.KeyUtil;
 import sun.security.util.ObjectIdentifier;
 import sun.security.pkcs10.PKCS10;
@@ -116,6 +117,7 @@
     private String keyAlgName = null;
     private boolean verbose = false;
     private int keysize = -1;
+    private String groupName = null;
     private boolean rfc = false;
     private long validity = (long)90;
     private String alias = null;
@@ -202,7 +204,7 @@
             STORETYPE, PROVIDERNAME, ADDPROVIDER, PROVIDERCLASS,
             PROVIDERPATH, V, PROTECTED),
         GENKEYPAIR("Generates.a.key.pair",
-            ALIAS, KEYALG, KEYSIZE, SIGALG, DESTALIAS, DNAME,
+            ALIAS, KEYALG, KEYSIZE, CURVENAME, SIGALG, DESTALIAS, DNAME,
             STARTDATE, EXT, VALIDITY, KEYPASS, KEYSTORE,
             STOREPASS, STORETYPE, PROVIDERNAME, ADDPROVIDER,
             PROVIDERCLASS, PROVIDERPATH, V, PROTECTED),
@@ -314,6 +316,7 @@
     // in the optionsSet.contains() block in parseArgs().
     enum Option {
         ALIAS("alias", "<alias>", "alias.name.of.the.entry.to.process"),
+        CURVENAME("groupname", "<name>", "groupname.option.help"),
         DESTALIAS("destalias", "<alias>", "destination.alias"),
         DESTKEYPASS("destkeypass", "<arg>", "destination.key.password"),
         DESTKEYSTORE("destkeystore", "<keystore>", "destination.keystore.name"),
@@ -586,6 +589,8 @@
                 dname = args[++i];
             } else if (collator.compare(flags, "-keysize") == 0) {
                 keysize = Integer.parseInt(args[++i]);
+            } else if (collator.compare(flags, "-groupname") == 0) {
+                groupName = args[++i];
             } else if (collator.compare(flags, "-keyalg") == 0) {
                 keyAlgName = args[++i];
             } else if (collator.compare(flags, "-sigalg") == 0) {
@@ -1119,7 +1124,7 @@
             if (keyAlgName == null) {
                 keyAlgName = "DSA";
             }
-            doGenKeyPair(alias, dname, keyAlgName, keysize, sigAlgName);
+            doGenKeyPair(alias, dname, keyAlgName, keysize, groupName, sigAlgName);
             kssave = true;
         } else if (command == GENSECKEY) {
             if (keyAlgName == null) {
@@ -1793,16 +1798,28 @@
      * Creates a new key pair and self-signed certificate.
      */
     private void doGenKeyPair(String alias, String dname, String keyAlgName,
-                              int keysize, String sigAlgName)
+                              int keysize, String groupName, String sigAlgName)
         throws Exception
     {
-        if (keysize == -1) {
-            if ("EC".equalsIgnoreCase(keyAlgName)) {
-                keysize = SecurityProviderConstants.DEF_EC_KEY_SIZE;
-            } else if ("RSA".equalsIgnoreCase(keyAlgName)) {
-                keysize = SecurityProviderConstants.DEF_RSA_KEY_SIZE;
-            } else if ("DSA".equalsIgnoreCase(keyAlgName)) {
-                keysize = SecurityProviderConstants.DEF_DSA_KEY_SIZE;
+        if (groupName != null) {
+            if (keysize != -1) {
+                throw new Exception(rb.getString("groupname.keysize.coexist"));
+            }
+        } else {
+            if (keysize == -1) {
+                if ("EC".equalsIgnoreCase(keyAlgName)) {
+                    keysize = SecurityProviderConstants.DEF_EC_KEY_SIZE;
+                } else if ("RSA".equalsIgnoreCase(keyAlgName)) {
+                    keysize = SecurityProviderConstants.DEF_RSA_KEY_SIZE;
+                } else if ("DSA".equalsIgnoreCase(keyAlgName)) {
+                    keysize = SecurityProviderConstants.DEF_DSA_KEY_SIZE;
+                }
+            } else {
+                if ("EC".equalsIgnoreCase(keyAlgName)) {
+                    weakWarnings.add(String.format(
+                            rb.getString("deprecate.keysize.for.ec"),
+                            ecGroupNameForSize(keysize)));
+                }
             }
         }
 
@@ -1829,7 +1846,13 @@
             x500Name = new X500Name(dname);
         }
 
-        keypair.generate(keysize);
+        if (groupName != null) {
+            keypair.generate(groupName);
+        } else {
+            // This covers keysize both specified and unspecified
+            keypair.generate(keysize);
+        }
+
         PrivateKey privKey = keypair.getPrivateKey();
 
         CertificateExtensions ext = createV3Extensions(
@@ -1861,6 +1884,13 @@
         keyStore.setKeyEntry(alias, privKey, keyPass, chain);
     }
 
+    private String ecGroupNameForSize(int size) throws Exception {
+        AlgorithmParameters ap = AlgorithmParameters.getInstance("EC");
+        ap.init(new ECKeySizeParameterSpec(size));
+        // The following line assumes the toString value is "name (oid)"
+        return ap.toString().split(" ")[0];
+    }
+
     /**
      * Clones an entry
      * @param orig original alias
--- a/src/java.base/share/classes/sun/security/tools/keytool/Resources.java	Tue Nov 13 16:17:24 2018 -0800
+++ b/src/java.base/share/classes/sun/security/tools/keytool/Resources.java	Wed Nov 14 08:46:25 2018 +0800
@@ -99,6 +99,8 @@
         // keytool: help: options
         {"alias.name.of.the.entry.to.process",
                 "alias name of the entry to process"}, //-alias
+        {"groupname.option.help",
+                "Group name. For example, an Elliptic Curve name."}, //-groupname
         {"destination.alias",
                 "destination alias"}, //-destalias
         {"destination.key.password",
@@ -290,6 +292,10 @@
                 "Alias <{0}> does not exist"},
         {"Alias.alias.has.no.certificate",
                 "Alias <{0}> has no certificate"},
+        {"groupname.keysize.coexist",
+                "Cannot specify both -groupname and -keysize"},
+        {"deprecate.keysize.for.ec",
+                "Specifying -keysize for generating EC keys is deprecated, please use \"-groupname %s\" instead."},
         {"Key.pair.not.generated.alias.alias.already.exists",
                 "Key pair not generated, alias <{0}> already exists"},
         {"Generating.keysize.bit.keyAlgName.key.pair.and.self.signed.certificate.sigAlgName.with.a.validity.of.validality.days.for",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/tools/keytool/GroupName.java	Wed Nov 14 08:46:25 2018 +0800
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2018, 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.
+ */
+
+import jdk.test.lib.Asserts;
+import jdk.test.lib.SecurityTools;
+import jdk.test.lib.process.OutputAnalyzer;
+
+import java.io.File;
+import java.security.KeyStore;
+import java.security.interfaces.ECKey;
+
+/**
+ * @test
+ * @bug 8213400
+ * @summary Support choosing group name in keytool keypair generation
+ * @library /test/lib
+ */
+
+public class GroupName {
+
+    private static final String COMMON = "-keystore ks "
+            + "-storepass changeit -keypass changeit -debug";
+
+    public static void main(String[] args) throws Throwable {
+        gen("a", "-keyalg RSA -groupname secp256r1")
+                .shouldHaveExitValue(1);
+
+        gen("b", "-keyalg EC")
+                .shouldHaveExitValue(0)
+                .shouldNotContain("Specifying -keysize for generating EC keys is deprecated");
+        checkCurveName("b", "secp256r1");
+
+        gen("c", "-keyalg EC -keysize 256")
+                .shouldHaveExitValue(0)
+                .shouldContain("Specifying -keysize for generating EC keys is deprecated")
+                .shouldContain("please use \"-groupname secp256r1\" instead.");
+        checkCurveName("c", "secp256r1");
+
+        gen("d", "-keyalg EC -keysize 256 -groupname secp256r1")
+                .shouldHaveExitValue(1)
+                .shouldContain("Cannot specify both -groupname and -keysize");
+
+        gen("e", "-keyalg EC -groupname secp256r1")
+                .shouldHaveExitValue(0)
+                .shouldNotContain("Specifying -keysize for generating EC keys is deprecated");
+        checkCurveName("e", "secp256r1");
+
+        gen("f", "-keyalg EC -groupname brainpoolP256r1")
+                .shouldHaveExitValue(0)
+                .shouldNotContain("Specifying -keysize for generating EC keys is deprecated");
+        checkCurveName("f", "brainpoolP256r1");
+    }
+
+    private static void checkCurveName(String a, String name)
+            throws Exception {
+        KeyStore ks = KeyStore.getInstance(new File("ks"), "changeit".toCharArray());
+        ECKey key = (ECKey)ks.getCertificate(a).getPublicKey();
+        // The following check is highly implementation dependent. In OpenJDK,
+        // params.toString() should contain all alternative names and the OID.
+        Asserts.assertTrue(key.getParams().toString().contains(name));
+    }
+
+    private static OutputAnalyzer kt(String cmd) throws Throwable {
+        return SecurityTools.keytool(COMMON + " " + cmd);
+    }
+
+    private static OutputAnalyzer gen(String a, String extra) throws Throwable {
+        return kt("-genkeypair -alias " + a + " -dname CN=" + a + " " + extra);
+    }
+}