8221257: Improve serial number generation mechanism for keytool -gencert
authorweijun
Sat, 30 Mar 2019 16:32:23 +0800
changeset 54346 b7ebff3e4e69
parent 54345 1042cac8bc2a
child 54347 235883996bc7
8221257: Improve serial number generation mechanism for keytool -gencert Reviewed-by: xuelei, mullan
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/x509/CertificateSerialNumber.java
test/jdk/sun/security/tools/keytool/Serial64.java
--- a/src/java.base/share/classes/sun/security/tools/keytool/CertAndKeyGen.java	Fri Mar 29 18:25:39 2019 -0700
+++ b/src/java.base/share/classes/sun/security/tools/keytool/CertAndKeyGen.java	Sat Mar 30 16:32:23 2019 +0800
@@ -287,8 +287,11 @@
             // Add all mandatory attributes
             info.set(X509CertInfo.VERSION,
                      new CertificateVersion(CertificateVersion.V3));
-            info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(
-                    new java.util.Random().nextInt() & 0x7fffffff));
+            if (prng == null) {
+                prng = new SecureRandom();
+            }
+            info.set(X509CertInfo.SERIAL_NUMBER,
+                    CertificateSerialNumber.newRandom64bit(prng));
             AlgorithmId algID = AlgorithmId.getWithParameterSpec(sigAlg, params);
             info.set(X509CertInfo.ALGORITHM_ID,
                      new CertificateAlgorithmId(algID));
--- a/src/java.base/share/classes/sun/security/tools/keytool/Main.java	Fri Mar 29 18:25:39 2019 -0700
+++ b/src/java.base/share/classes/sun/security/tools/keytool/Main.java	Sat Mar 30 16:32:23 2019 +0800
@@ -37,6 +37,7 @@
 import java.security.Key;
 import java.security.PublicKey;
 import java.security.PrivateKey;
+import java.security.SecureRandom;
 import java.security.Signature;
 import java.security.Timestamp;
 import java.security.UnrecoverableEntryException;
@@ -1436,8 +1437,8 @@
                 .getDefaultAlgorithmParameterSpec(sigAlgName, privateKey);
         AlgorithmId algID = AlgorithmId.getWithParameterSpec(sigAlgName, params);
         info.set(X509CertInfo.VALIDITY, interval);
-        info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(
-                    new java.util.Random().nextInt() & 0x7fffffff));
+        info.set(X509CertInfo.SERIAL_NUMBER,
+                CertificateSerialNumber.newRandom64bit(new SecureRandom()));
         info.set(X509CertInfo.VERSION,
                     new CertificateVersion(CertificateVersion.V3));
         info.set(X509CertInfo.ALGORITHM_ID,
@@ -2947,8 +2948,8 @@
         certInfo.set(X509CertInfo.VALIDITY, interval);
 
         // Make new serial number
-        certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(
-                    new java.util.Random().nextInt() & 0x7fffffff));
+        certInfo.set(X509CertInfo.SERIAL_NUMBER,
+                CertificateSerialNumber.newRandom64bit(new SecureRandom()));
 
         // Set owner and issuer fields
         X500Name owner;
--- a/src/java.base/share/classes/sun/security/x509/CertificateSerialNumber.java	Fri Mar 29 18:25:39 2019 -0700
+++ b/src/java.base/share/classes/sun/security/x509/CertificateSerialNumber.java	Sat Mar 30 16:32:23 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2011, 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
@@ -29,6 +29,7 @@
 import java.io.OutputStream;
 import java.math.BigInteger;
 import java.util.Enumeration;
+import java.util.Random;
 
 import sun.security.util.*;
 
@@ -179,4 +180,16 @@
     public String getName() {
         return (NAME);
     }
+
+    /**
+     * Generates a new random serial number.
+     */
+    public static CertificateSerialNumber newRandom64bit(Random rand) {
+        while (true) {
+            BigInteger b = new BigInteger(64, rand);
+            if (b.signum() != 0) {
+                return new CertificateSerialNumber(b);
+            }
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/tools/keytool/Serial64.java	Sat Mar 30 16:32:23 2019 +0800
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ *
+ * 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 8221257
+ * @summary Improve serial number generation mechanism for keytool -gencert
+ * @library /test/lib
+ * @key randomness
+ */
+
+import jdk.test.lib.Asserts;
+import jdk.test.lib.SecurityTools;
+import jdk.test.lib.process.OutputAnalyzer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.math.BigInteger;
+import java.security.KeyStore;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Serial64 {
+
+    static List<BigInteger> numbers = new ArrayList<>();
+
+    public static void main(String[] args) throws Exception {
+
+        // 10 Self-signed certs and issued certs
+        genkeypair("ca");
+        genkeypair("user");
+        for (int i = 0; i < 8; i++) {
+            gencert("ca", "user");
+        }
+
+        numbers.forEach(b -> System.out.println(b.toString(16)));
+
+        // Must be positive, therefore never zero.
+        Asserts.assertTrue(numbers.stream()
+                .allMatch(b -> b.signum() == 1));
+
+        // At least one should be 64 bit. There is a chance of
+        // 2^-10 this would fail.
+        Asserts.assertTrue(numbers.stream()
+                .anyMatch(b -> b.bitLength() == 64));
+    }
+
+    static OutputAnalyzer keytool(String s) throws Exception {
+        return SecurityTools.keytool(
+                "-storepass changeit -keypass changeit "
+                        + "-keystore ks -keyalg rsa " + s);
+    }
+
+    static void genkeypair(String a) throws Exception {
+        keytool("-genkeypair -alias " + a + " -dname CN=" + a)
+                .shouldHaveExitValue(0);
+        numbers.add(((X509Certificate)KeyStore.getInstance(
+                new File("ks"), "changeit".toCharArray())
+                    .getCertificate(a)).getSerialNumber());
+    }
+
+    static void gencert(String signer, String owner)
+            throws Exception {
+        keytool("-certreq -alias " + owner + " -file req")
+                .shouldHaveExitValue(0);
+        keytool("-gencert -alias " + signer + " -infile req -outfile cert")
+                .shouldHaveExitValue(0);
+        try (FileInputStream fis = new FileInputStream("cert")) {
+            numbers.add(((X509Certificate)CertificateFactory.getInstance("X.509")
+                    .generateCertificate(fis)).getSerialNumber());
+        }
+    }
+}