--- /dev/null 2017-01-19 08:23:44.229040998 +0800 +++ new/test/sun/security/tools/keytool/WeakAlg.java 2017-01-23 18:02:01.463526100 +0800 @@ -0,0 +1,496 @@ +/* + * 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 + * @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 + * @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 { + + Files.deleteIfExists(Paths.get("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("'s MD5withRSA.*risk") + .shouldMatch("'s 512-bit RSA key.*risk"); + kt("-list -v") + .shouldContain("Warning:") + .shouldMatch("'s MD5withRSA.*risk") + .shouldContain("MD5withRSA (weak)") + .shouldMatch("'s 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 output's MD2withRSA.*risk"); + + checkCertReq("a", "-sigalg MD5withRSA", "MD5withRSA"); + gencert("c-a", "") + .shouldContain("Warning:") + .shouldMatch("The input's MD5withRSA.*risk"); + gencert("c-a", "-sigalg MD2withRSA") + .shouldContain("Warning:") + .shouldMatch("The input's MD5withRSA.*risk") + .shouldMatch("The output's MD2withRSA.*risk"); + + checkCertReq("b", "", "512-bit RSA key"); + gencert("c-b", "") + .shouldContain("Warning:") + .shouldMatch("The input's 512-bit RSA key.*risk") + .shouldMatch("The output's 512-bit RSA key.*risk"); + + checkCertReq("c", "", null); + gencert("a-c", "") + .shouldContain("Warning:") + .shouldMatch("The issuer's 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's 512-bit RSA key.*risk"); + + // -importcert + checkImport(); + + // -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 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("MD5withRSA.*risk") + .shouldContain("Do you still want to add it?"); + kt("-importcert -alias d -file a.cert -noprompt") + .shouldContain("Warning") + .shouldMatch("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("MD5withRSA.*risk") + .shouldContain("Trust this certificate?"); + kt("-importcert -alias d -file a.cert -noprompt") + .shouldContain("Warning") + .shouldMatch("MD5withRSA.*risk") + .shouldNotContain("[no]"); + + // cert is self-signed cacerts + String weakSigAlgCA = null; + KeyStore ks = KeyStoreUtil.getCacertsKeyStore(); + if (ks != null) { + DisabledAlgorithmConstraints disabledCheck = + new DisabledAlgorithmConstraints( + DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS); + Set 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) { + kt("-delete -alias d"); + kt("-importcert -alias d -trustcacerts -file ca.cert", "no") + .shouldContain("Certificate already exists in system-wide CA") + .shouldContain("Warning") + .shouldMatch(weakSigAlgCA + ".*risk") + .shouldContain("Do you still want to add it to your own keystore?"); + kt("-importcert -alias d -file ca.cert -noprompt") + .shouldContain("Warning") + .shouldMatch(weakSigAlgCA + ".*risk") + .shouldNotContain("[no]"); + } + + // a non self-signed weak cert + reStore(); + certreq("b", ""); + gencert("c-b", ""); + kt("-importcert -alias d -file c-b.cert", "no") // weak but trusted + .shouldContain("Warning") + .shouldNotContain("512-bit RSA key (weak)") + .shouldMatch("The input's 512-bit RSA key.*risk") + .shouldContain("Trust this certificate?"); + kt("-importcert -alias d -file c-b.cert -noprompt") + .shouldContain("Warning") + .shouldMatch("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("512-bit RSA key.*risk") + .shouldContain("Trust this certificate?"); + kt("-importcert -alias d -file c-b.cert -noprompt") + .shouldContain("Warning") + .shouldMatch("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(); + + kt("-importcert -alias a -file c-a.cert") + .shouldNotContain("Warning"); + + kt("-importcert -alias b -file c-b.cert", "no") + .shouldContain("Warning") + .shouldMatch("The input's 512-bit RSA key.*risk") + .shouldContain("Trust this certificate?"); + + reStore(); + gencert("b-a", ""); + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + bout.write(Files.readAllBytes(Paths.get("b-a.cert"))); + bout.write(Files.readAllBytes(Paths.get("c-b.cert"))); + Files.write(Paths.get("c-b-a.cert"), bout.toByteArray()); + + + kt("-delete -alias b"); + + kt("-importcert -alias a -file c-b-a.cert", "no") + .shouldContain("Warning") + .shouldMatch("Reply #2 of 2's 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's 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's 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's 512-bit RSA key.*risk") + .shouldNotContain("[no]"); + + reStore(); + } + + 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(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(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(bad + ".*risk"); + } + + oa = kt("-printcertreq -file " + alias + ".req"); + if (bad == null) { + oa.shouldNotContain("Warning") + .shouldNotContain("(weak)"); + } else { + oa.shouldContain("Warning") + .shouldMatch(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(bad + ".*risk"); + } + + oa = kt("-exportcert -alias " + alias + " -file " + alias + ".cert"); + if (bad == null) { + oa.shouldNotContain("Warning"); + } else { + oa.shouldContain("Warning") + .shouldMatch(bad + ".*risk"); + } + + oa = kt("-exportcert -rfc -alias " + alias + " -file " + alias + ".cert"); + if (bad == null) { + oa.shouldNotContain("Warning"); + } else { + oa.shouldContain("Warning") + .shouldMatch(bad + ".*risk"); + } + + oa = kt("-printcert -rfc -file " + alias + ".cert"); + if (bad == null) { + oa.shouldNotContain("Warning"); + } else { + oa.shouldContain("Warning") + .shouldMatch(bad + ".*risk"); + } + + oa = kt("-list -alias " + alias); + if (bad == null) { + oa.shouldNotContain("Warning"); + } else { + oa.shouldContain("Warning") + .shouldMatch(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(bad + ".*risk"); + } + + oa = kt("-list -v -alias " + alias); + if (bad == null) { + oa.shouldNotContain("Warning"); + } else { + oa.shouldContain("Warning") + .shouldContain(bad + " (weak)") + .shouldMatch(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 + + " -keyalg RSA -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); + } +}