8259401: Add checking to jarsigner to warn weak algorithms used in signer’s cert chain

Reviewed-by: mullan, weijun, rhalade
diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java
index 23e0472..3bc071d 100644
--- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java
+++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2021, 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
@@ -1401,6 +1401,35 @@
         }
     }
 
+    private static String checkWeakKey(PublicKey key) {
+        int kLen = KeyUtil.getKeySize(key);
+        if (DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
+            if (LEGACY_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
+                if (kLen >= 0) {
+                    return String.format(rb.getString("key.bit"), kLen);
+                } else {
+                    return rb.getString("unknown.size");
+                }
+            } else {
+                return String.format(rb.getString("key.bit.weak"), kLen);
+            }
+        } else {
+           return String.format(rb.getString("key.bit.disabled"), kLen);
+        }
+    }
+
+    private static String checkWeakAlg(String alg) {
+        if (DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, alg, null)) {
+            if (LEGACY_CHECK.permits(SIG_PRIMITIVE_SET, alg, null)) {
+                return alg;
+            } else {
+                return String.format(rb.getString("with.weak"), alg);
+            }
+        } else {
+            return String.format(rb.getString("with.disabled"), alg);
+        }
+    }
+
     private static MessageFormat validityTimeForm = null;
     private static MessageFormat notYetTimeForm = null;
     private static MessageFormat expiredTimeForm = null;
@@ -1444,12 +1473,31 @@
         }
 
         if (x509Cert != null) {
+            PublicKey key = x509Cert.getPublicKey();
+            String sigalg = x509Cert.getSigAlgName();
 
-            certStr.append("\n").append(tab).append("[");
-
+            // Process the certificate in the signer's cert chain to see if
+            // weak algorithms are used, and provide warnings as needed.
             if (trustedCerts.contains(x509Cert)) {
+                // If the cert is trusted, only check its key size, but not its
+                // signature algorithm.
+                certStr.append("\n").append(tab)
+                        .append("Signature algorithm: ")
+                        .append(sigalg)
+                        .append(rb.getString("COMMA"))
+                        .append(checkWeakKey(key));
+
+                certStr.append("\n").append(tab).append("[");
                 certStr.append(rb.getString("trusted.certificate"));
             } else {
+                certStr.append("\n").append(tab)
+                        .append("Signature algorithm: ")
+                        .append(checkWeakAlg(sigalg))
+                        .append(rb.getString("COMMA"))
+                        .append(checkWeakKey(key));
+
+                certStr.append("\n").append(tab).append("[");
+
                 Date notAfter = x509Cert.getNotAfter();
                 try {
                     boolean printValidity = true;
diff --git a/test/jdk/sun/security/tools/jarsigner/CheckSignerCertChain.java b/test/jdk/sun/security/tools/jarsigner/CheckSignerCertChain.java
new file mode 100644
index 0000000..157abb4
--- /dev/null
+++ b/test/jdk/sun/security/tools/jarsigner/CheckSignerCertChain.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2021, 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 8259401
+ * @summary Check certificates in signer's cert chain to see if warning emitted
+ * @library /test/lib
+ */
+
+import jdk.test.lib.SecurityTools;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.util.JarUtils;
+
+import java.nio.file.Path;
+
+public class CheckSignerCertChain {
+
+    static OutputAnalyzer kt(String cmd, String ks) throws Exception {
+        return SecurityTools.keytool("-storepass changeit " + cmd +
+                " -keystore " + ks);
+    }
+
+    static void gencert(String owner, String cmd) throws Exception {
+        kt("-certreq -alias " + owner + " -file tmp.req", "ks");
+        kt("-gencert -infile tmp.req -outfile tmp.cert " + cmd, "ks");
+        kt("-importcert -alias " + owner + " -file tmp.cert", "ks");
+    }
+
+    public static void main(String[] args) throws Exception {
+
+        // root certificate using SHA1withRSA and 1024-bit key
+        System.out.println("Generating a root cert using SHA1withRSA and 1024-bit key");
+        kt("-genkeypair -keyalg rsa -alias ca -dname CN=CA -ext bc:c " +
+                "-keysize 1024 -sigalg SHA1withRSA", "ks");
+        kt("-genkeypair -keyalg rsa -alias ca1 -dname CN=CA1", "ks");
+        kt("-genkeypair -keyalg rsa -alias e1 -dname CN=E1", "ks");
+
+        // intermediate certificate using SHA1withRSA and 2048-bit key
+        System.out.println("Generating an intermediate cert using SHA1withRSA and 2048-bit key");
+        gencert("ca1", "-alias ca -ext san=dns:ca1 -ext bc:c " +
+                "-sigalg SHA1withRSA ");
+
+        // end entity certificate using SHA256withRSA and 2048-bit key
+        System.out.println("Generating an end entity cert using SHA256withRSA and 2048-bit key");
+        gencert("e1", "-alias ca1 -ext san=dns:e1 ");
+
+        JarUtils.createJarFile(Path.of("a.jar"), Path.of("."), Path.of("ks"));
+
+        SecurityTools.jarsigner("-keystore ks -storepass changeit " +
+                "-signedjar signeda.jar " +
+                "-sigalg SHA256withRSA " +
+                "-verbose" +
+                " a.jar e1")
+                .shouldContain("Signature algorithm: SHA1withRSA (weak), 2048-bit key")
+                // For trusted cert, warning should be generated for its weak 1024-bit
+                // key, but not for its SHA1withRSA algorithm.
+                .shouldContain("Signature algorithm: SHA1withRSA, 1024-bit key (weak)")
+                .shouldHaveExitValue(0);
+
+        kt("-exportcert -alias ca -rfc -file cacert", "ks");
+        kt("-importcert -noprompt -file cacert", "caks");
+
+        SecurityTools.jarsigner("-verify -certs signeda.jar " +
+                "-keystore caks -storepass changeit -verbose -debug")
+                .shouldContain("Signature algorithm: SHA1withRSA (weak), 2048-bit key")
+                // For trusted cert, warning should be generated for its weak 1024-bit
+                // key, but not for its SHA1withRSA algorithm.
+                .shouldContain("Signature algorithm: SHA1withRSA, 1024-bit key (weak)")
+                .shouldHaveExitValue(0);
+    }
+}