test/jdk/sun/security/tools/keytool/WeakAlg.java
changeset 47216 71c04702a3d5
parent 45839 6df5e24443fc
child 47420 a2bf68a0365f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/tools/keytool/WeakAlg.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,613 @@
+/*
+ * Copyright (c) 2017, 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 8171319 8177569
+ * @summary keytool should print out warnings when reading or generating
+  *         cert/cert req using weak algorithms
+ * @library /test/lib
+ * @modules java.base/sun.security.tools.keytool
+ *          java.base/sun.security.tools
+ *          java.base/sun.security.util
+ * @build jdk.test.lib.SecurityTools
+ *        jdk.test.lib.Utils
+ *        jdk.test.lib.Asserts
+ *        jdk.test.lib.JDKToolFinder
+ *        jdk.test.lib.JDKToolLauncher
+ *        jdk.test.lib.Platform
+ *        jdk.test.lib.process.*
+ * @run main/othervm/timeout=600 -Duser.language=en -Duser.country=US WeakAlg
+ */
+
+import jdk.test.lib.SecurityTools;
+import jdk.test.lib.process.OutputAnalyzer;
+import sun.security.tools.KeyStoreUtil;
+import sun.security.util.DisabledAlgorithmConstraints;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.security.CryptoPrimitive;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class WeakAlg {
+
+    public static void main(String[] args) throws Throwable {
+
+        rm("ks");
+
+        // -genkeypair, and -printcert, -list -alias, -exportcert
+        // (w/ different formats)
+        checkGenKeyPair("a", "-keyalg RSA -sigalg MD5withRSA", "MD5withRSA");
+        checkGenKeyPair("b", "-keyalg RSA -keysize 512", "512-bit RSA key");
+        checkGenKeyPair("c", "-keyalg RSA", null);
+
+        kt("-list")
+                .shouldContain("Warning:")
+                .shouldMatch("<a>.*MD5withRSA.*risk")
+                .shouldMatch("<b>.*512-bit RSA key.*risk");
+        kt("-list -v")
+                .shouldContain("Warning:")
+                .shouldMatch("<a>.*MD5withRSA.*risk")
+                .shouldContain("MD5withRSA (weak)")
+                .shouldMatch("<b>.*512-bit RSA key.*risk")
+                .shouldContain("512-bit RSA key (weak)");
+
+        // Multiple warnings for multiple cert in -printcert
+        // or -list or -exportcert
+
+        // -certreq, -printcertreq, -gencert
+        checkCertReq("a", "", null);
+        gencert("c-a", "")
+                .shouldNotContain("Warning"); // new sigalg is not weak
+        gencert("c-a", "-sigalg MD2withRSA")
+                .shouldContain("Warning:")
+                .shouldMatch("The generated certificate.*MD2withRSA.*risk");
+
+        checkCertReq("a", "-sigalg MD5withRSA", "MD5withRSA");
+        gencert("c-a", "")
+                .shouldContain("Warning:")
+                .shouldMatch("The certificate request.*MD5withRSA.*risk");
+        gencert("c-a", "-sigalg MD2withRSA")
+                .shouldContain("Warning:")
+                .shouldMatch("The certificate request.*MD5withRSA.*risk")
+                .shouldMatch("The generated certificate.*MD2withRSA.*risk");
+
+        checkCertReq("b", "", "512-bit RSA key");
+        gencert("c-b", "")
+                .shouldContain("Warning:")
+                .shouldMatch("The certificate request.*512-bit RSA key.*risk")
+                .shouldMatch("The generated certificate.*512-bit RSA key.*risk");
+
+        checkCertReq("c", "", null);
+        gencert("a-c", "")
+                .shouldContain("Warning:")
+                .shouldMatch("The issuer.*MD5withRSA.*risk");
+
+        // but the new cert is not weak
+        kt("-printcert -file a-c.cert")
+                .shouldNotContain("Warning")
+                .shouldNotContain("weak");
+
+        gencert("b-c", "")
+                .shouldContain("Warning:")
+                .shouldMatch("The issuer.*512-bit RSA key.*risk");
+
+        // -importcert
+        checkImport();
+
+        // -importkeystore
+        checkImportKeyStore();
+
+        // -gencrl, -printcrl
+
+        checkGenCRL("a", "", null);
+        checkGenCRL("a", "-sigalg MD5withRSA", "MD5withRSA");
+        checkGenCRL("b", "", "512-bit RSA key");
+        checkGenCRL("c", "", null);
+
+        kt("-delete -alias b");
+        kt("-printcrl -file b.crl")
+                .shouldContain("WARNING: not verified");
+    }
+
+    static void checkImportKeyStore() throws Exception {
+
+        saveStore();
+
+        rm("ks");
+        kt("-importkeystore -srckeystore ks2 -srcstorepass changeit")
+                .shouldContain("3 entries successfully imported")
+                .shouldContain("Warning")
+                .shouldMatch("<b>.*512-bit RSA key.*risk")
+                .shouldMatch("<a>.*MD5withRSA.*risk");
+
+        rm("ks");
+        kt("-importkeystore -srckeystore ks2 -srcstorepass changeit -srcalias a")
+                .shouldContain("Warning")
+                .shouldMatch("<a>.*MD5withRSA.*risk");
+
+        reStore();
+    }
+
+    static void checkImport() throws Exception {
+
+        saveStore();
+
+        // add trusted cert
+
+        // cert already in
+        kt("-importcert -alias d -file a.cert", "no")
+                .shouldContain("Certificate already exists in keystore")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*MD5withRSA.*risk")
+                .shouldContain("Do you still want to add it?");
+        kt("-importcert -alias d -file a.cert -noprompt")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*MD5withRSA.*risk")
+                .shouldNotContain("[no]");
+
+        // cert is self-signed
+        kt("-delete -alias a");
+        kt("-delete -alias d");
+        kt("-importcert -alias d -file a.cert", "no")
+                .shouldContain("Warning")
+                .shouldContain("MD5withRSA (weak)")
+                .shouldMatch("The input.*MD5withRSA.*risk")
+                .shouldContain("Trust this certificate?");
+        kt("-importcert -alias d -file a.cert -noprompt")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*MD5withRSA.*risk")
+                .shouldNotContain("[no]");
+
+        // JDK-8177569: no warning for sigalg of trusted cert
+        String weakSigAlgCA = null;
+        KeyStore ks = KeyStoreUtil.getCacertsKeyStore();
+        if (ks != null) {
+            DisabledAlgorithmConstraints disabledCheck =
+                    new DisabledAlgorithmConstraints(
+                            DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
+            Set<CryptoPrimitive> sigPrimitiveSet = Collections
+                    .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
+
+            for (String s : Collections.list(ks.aliases())) {
+                if (ks.isCertificateEntry(s)) {
+                    X509Certificate c = (X509Certificate)ks.getCertificate(s);
+                    String sigAlg = c.getSigAlgName();
+                    if (!disabledCheck.permits(sigPrimitiveSet, sigAlg, null)) {
+                        weakSigAlgCA = sigAlg;
+                        Files.write(Paths.get("ca.cert"),
+                                ks.getCertificate(s).getEncoded());
+                        break;
+                    }
+                }
+            }
+        }
+        if (weakSigAlgCA != null) {
+            // The following 2 commands still have a warning on why not using
+            // the -cacerts option directly.
+            kt("-list -keystore " + KeyStoreUtil.getCacerts())
+                    .shouldNotContain("risk");
+            kt("-list -v -keystore " + KeyStoreUtil.getCacerts())
+                    .shouldNotContain("risk");
+
+            // -printcert will always show warnings
+            kt("-printcert -file ca.cert")
+                    .shouldContain("name: " + weakSigAlgCA + " (weak)")
+                    .shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + weakSigAlgCA + ".*risk");
+            kt("-printcert -file ca.cert -trustcacerts") // -trustcacerts useless
+                    .shouldContain("name: " + weakSigAlgCA + " (weak)")
+                    .shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + weakSigAlgCA + ".*risk");
+
+            // Importing with -trustcacerts ignore CA cert's sig alg
+            kt("-delete -alias d");
+            kt("-importcert -alias d -trustcacerts -file ca.cert", "no")
+                    .shouldContain("Certificate already exists in system-wide CA")
+                    .shouldNotContain("risk")
+                    .shouldContain("Do you still want to add it to your own keystore?");
+            kt("-importcert -alias d -trustcacerts -file ca.cert -noprompt")
+                    .shouldNotContain("risk")
+                    .shouldNotContain("[no]");
+
+            // but not without -trustcacerts
+            kt("-delete -alias d");
+            kt("-importcert -alias d -file ca.cert", "no")
+                    .shouldContain("name: " + weakSigAlgCA + " (weak)")
+                    .shouldContain("Warning")
+                    .shouldMatch("The input.*" + weakSigAlgCA + ".*risk")
+                    .shouldContain("Trust this certificate?");
+            kt("-importcert -alias d -file ca.cert -noprompt")
+                    .shouldContain("Warning")
+                    .shouldMatch("The input.*" + weakSigAlgCA + ".*risk")
+                    .shouldNotContain("[no]");
+        }
+
+        // a non self-signed weak cert
+        reStore();
+        certreq("b", "");
+        gencert("c-b", "");
+        kt("-importcert -alias d -file c-b.cert")   // weak only, no prompt
+                .shouldContain("Warning")
+                .shouldNotContain("512-bit RSA key (weak)")
+                .shouldMatch("The input.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        kt("-delete -alias b");
+        kt("-delete -alias c");
+        kt("-delete -alias d");
+
+        kt("-importcert -alias d -file c-b.cert", "no") // weak and not trusted
+                .shouldContain("Warning")
+                .shouldContain("512-bit RSA key (weak)")
+                .shouldMatch("The input.*512-bit RSA key.*risk")
+                .shouldContain("Trust this certificate?");
+        kt("-importcert -alias d -file c-b.cert -noprompt")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        // a non self-signed strong cert
+        reStore();
+        certreq("a", "");
+        gencert("c-a", "");
+        kt("-importcert -alias d -file c-a.cert") // trusted
+                .shouldNotContain("Warning")
+                .shouldNotContain("[no]");
+
+        kt("-delete -alias a");
+        kt("-delete -alias c");
+        kt("-delete -alias d");
+
+        kt("-importcert -alias d -file c-a.cert", "no") // not trusted
+                .shouldNotContain("Warning")
+                .shouldContain("Trust this certificate?");
+        kt("-importcert -alias d -file c-a.cert -noprompt")
+                .shouldNotContain("Warning")
+                .shouldNotContain("[no]");
+
+        // install reply
+
+        reStore();
+        certreq("c", "");
+        gencert("a-c", "");
+        kt("-importcert -alias c -file a-c.cert")
+                .shouldContain("Warning")
+                .shouldMatch("Issuer <a>.*MD5withRSA.*risk");
+
+        // JDK-8177569: no warning for sigalg of trusted cert
+        reStore();
+        // Change a into a TrustedCertEntry
+        kt("-exportcert -alias a -file a.cert");
+        kt("-delete -alias a");
+        kt("-importcert -alias a -file a.cert -noprompt");
+        kt("-list -alias a -v")
+                .shouldNotContain("weak")
+                .shouldNotContain("Warning");
+        // This time a is trusted and no warning on its weak sig alg
+        kt("-importcert -alias c -file a-c.cert")
+                .shouldNotContain("Warning");
+
+        reStore();
+
+        gencert("a-b", "");
+        gencert("b-c", "");
+
+        // Full chain with root
+        cat("a-a-b-c.cert", "b-c.cert", "a-b.cert", "a.cert");
+        kt("-importcert -alias c -file a-a-b-c.cert")   // only weak
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 3.*512-bit RSA key.*risk")
+                .shouldMatch("Reply #3 of 3.*MD5withRSA.*risk")
+                .shouldNotContain("[no]");
+
+        // Without root
+        cat("a-b-c.cert", "b-c.cert", "a-b.cert");
+        kt("-importcert -alias c -file a-b-c.cert")     // only weak
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
+                .shouldMatch("Issuer <a>.*MD5withRSA.*risk")
+                .shouldNotContain("[no]");
+
+        reStore();
+        gencert("b-a", "");
+
+        kt("-importcert -alias a -file b-a.cert")
+                .shouldContain("Warning")
+                .shouldMatch("Issuer <b>.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        kt("-importcert -alias a -file c-a.cert")
+                .shouldNotContain("Warning");
+
+        kt("-importcert -alias b -file c-b.cert")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        reStore();
+        gencert("b-a", "");
+
+        cat("c-b-a.cert", "b-a.cert", "c-b.cert");
+
+        kt("-printcert -file c-b-a.cert")
+                .shouldContain("Warning")
+                .shouldMatch("The certificate #2 of 2.*512-bit RSA key.*risk");
+
+        kt("-delete -alias b");
+
+        kt("-importcert -alias a -file c-b-a.cert")
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        kt("-delete -alias c");
+        kt("-importcert -alias a -file c-b-a.cert", "no")
+                .shouldContain("Top-level certificate in reply:")
+                .shouldContain("512-bit RSA key (weak)")
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
+                .shouldContain("Install reply anyway?");
+        kt("-importcert -alias a -file c-b-a.cert -noprompt")
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        reStore();
+    }
+
+    private static void cat(String dest, String... src) throws IOException {
+        System.out.println("---------------------------------------------");
+        System.out.printf("$ cat ");
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        for (String s : src) {
+            System.out.printf(s + " ");
+            bout.write(Files.readAllBytes(Paths.get(s)));
+        }
+        Files.write(Paths.get(dest), bout.toByteArray());
+        System.out.println("> " + dest);
+    }
+
+    static void checkGenCRL(String alias, String options, String bad) {
+
+        OutputAnalyzer oa = kt("-gencrl -alias " + alias
+                + " -id 1 -file " + alias + ".crl " + options);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The generated CRL.*" + bad + ".*risk");
+        }
+
+        oa = kt("-printcrl -file " + alias + ".crl");
+        if (bad == null) {
+            oa.shouldNotContain("Warning")
+                    .shouldContain("Verified by " + alias + " in keystore")
+                    .shouldNotContain("(weak");
+        } else {
+            oa.shouldContain("Warning:")
+                    .shouldMatch("The CRL.*" + bad + ".*risk")
+                    .shouldContain("Verified by " + alias + " in keystore")
+                    .shouldContain(bad + " (weak)");
+        }
+    }
+
+    static void checkCertReq(
+            String alias, String options, String bad) {
+
+        OutputAnalyzer oa = certreq(alias, options);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The generated certificate request.*" + bad + ".*risk");
+        }
+
+        oa = kt("-printcertreq -file " + alias + ".req");
+        if (bad == null) {
+            oa.shouldNotContain("Warning")
+                    .shouldNotContain("(weak)");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate request.*" + bad + ".*risk")
+                    .shouldContain(bad + " (weak)");
+        }
+    }
+
+    static void checkGenKeyPair(
+            String alias, String options, String bad) {
+
+        OutputAnalyzer oa = genkeypair(alias, options);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The generated certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-exportcert -alias " + alias + " -file " + alias + ".cert");
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-exportcert -rfc -alias " + alias + " -file " + alias + ".cert");
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-printcert -rfc -file " + alias + ".cert");
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-list -alias " + alias);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        // With cert content
+
+        oa = kt("-printcert -file " + alias + ".cert");
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldContain(bad + " (weak)")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-list -v -alias " + alias);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldContain(bad + " (weak)")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+    }
+
+    // This is slow, but real keytool process is launched.
+    static OutputAnalyzer kt1(String cmd, String... input) {
+        cmd = "-keystore ks -storepass changeit " +
+                "-keypass changeit " + cmd;
+        System.out.println("---------------------------------------------");
+        try {
+            SecurityTools.setResponse(input);
+            return SecurityTools.keytool(cmd);
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // Fast keytool execution by directly calling its main() method
+    static OutputAnalyzer kt(String cmd, String... input) {
+        PrintStream out = System.out;
+        PrintStream err = System.err;
+        InputStream ins = System.in;
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        ByteArrayOutputStream berr = new ByteArrayOutputStream();
+        boolean succeed = true;
+        try {
+            cmd = "-keystore ks -storepass changeit " +
+                    "-keypass changeit " + cmd;
+            System.out.println("---------------------------------------------");
+            System.out.println("$ keytool " + cmd);
+            System.out.println();
+            String feed = "";
+            if (input.length > 0) {
+                feed = Stream.of(input).collect(Collectors.joining("\n")) + "\n";
+            }
+            System.setIn(new ByteArrayInputStream(feed.getBytes()));
+            System.setOut(new PrintStream(bout));
+            System.setErr(new PrintStream(berr));
+            sun.security.tools.keytool.Main.main(
+                    cmd.trim().split("\\s+"));
+        } catch (Exception e) {
+            // Might be a normal exception when -debug is on or
+            // SecurityException (thrown by jtreg) when System.exit() is called
+            if (!(e instanceof SecurityException)) {
+                e.printStackTrace();
+            }
+            succeed = false;
+        } finally {
+            System.setOut(out);
+            System.setErr(err);
+            System.setIn(ins);
+        }
+        String sout = new String(bout.toByteArray());
+        String serr = new String(berr.toByteArray());
+        System.out.println("STDOUT:\n" + sout + "\nSTDERR:\n" + serr);
+        if (!succeed) {
+            throw new RuntimeException();
+        }
+        return new OutputAnalyzer(sout, serr);
+    }
+
+    static OutputAnalyzer genkeypair(String alias, String options) {
+        return kt("-genkeypair -alias " + alias + " -dname CN=" + alias
+                + " -storetype JKS " + options);
+    }
+
+    static OutputAnalyzer certreq(String alias, String options) {
+        return kt("-certreq -alias " + alias
+                + " -file " + alias + ".req " + options);
+    }
+
+    static OutputAnalyzer exportcert(String alias) {
+        return kt("-exportcert -alias " + alias + " -file " + alias + ".cert");
+    }
+
+    static OutputAnalyzer gencert(String relation, String options) {
+        int pos = relation.indexOf("-");
+        String issuer = relation.substring(0, pos);
+        String subject = relation.substring(pos + 1);
+        return kt(" -gencert -alias " + issuer + " -infile " + subject
+                + ".req -outfile " + relation + ".cert " + options);
+    }
+
+    static void saveStore() throws IOException {
+        System.out.println("---------------------------------------------");
+        System.out.println("$ cp ks ks2");
+        Files.copy(Paths.get("ks"), Paths.get("ks2"),
+                StandardCopyOption.REPLACE_EXISTING);
+    }
+
+    static void reStore() throws IOException {
+        System.out.println("---------------------------------------------");
+        System.out.println("$ cp ks2 ks");
+        Files.copy(Paths.get("ks2"), Paths.get("ks"),
+                StandardCopyOption.REPLACE_EXISTING);
+    }
+
+    static void rm(String s) throws IOException {
+        System.out.println("---------------------------------------------");
+        System.out.println("$ rm " + s);
+        Files.deleteIfExists(Paths.get(s));
+    }
+}