blob: bb7ae77737162de7a7af8e804c74abede533dbf6 [file] [log] [blame]
/*
* 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
* @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
+ " -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);
}
static void rm(String s) throws IOException {
System.out.println("---------------------------------------------");
System.out.println("$ rm " + s);
Files.deleteIfExists(Paths.get(s));
}
}