1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8171319
  27  * @summary keytool should print out warnings when reading or generating
  28   *         cert/cert req using weak algorithms
  29  * @library /test/lib
  30  * @modules java.base/sun.security.tools.keytool
  31  *          java.base/sun.security.tools
  32  *          java.base/sun.security.util
  33  * @run main/othervm/timeout=600 -Duser.language=en -Duser.country=US WeakAlg
  34  */
  35 
  36 import jdk.test.lib.SecurityTools;
  37 import jdk.test.lib.process.OutputAnalyzer;
  38 import sun.security.tools.KeyStoreUtil;
  39 import sun.security.util.DisabledAlgorithmConstraints;
  40 
  41 import java.io.ByteArrayInputStream;
  42 import java.io.ByteArrayOutputStream;
  43 import java.io.IOException;
  44 import java.io.InputStream;
  45 import java.io.PrintStream;
  46 import java.nio.file.Files;
  47 import java.nio.file.Paths;
  48 import java.nio.file.StandardCopyOption;
  49 import java.security.CryptoPrimitive;
  50 import java.security.KeyStore;
  51 import java.security.cert.X509Certificate;
  52 import java.util.Collections;
  53 import java.util.EnumSet;
  54 import java.util.Set;
  55 import java.util.stream.Collectors;
  56 import java.util.stream.Stream;
  57 
  58 public class WeakAlg {
  59 
  60     public static void main(String[] args) throws Throwable {
  61 
  62         Files.deleteIfExists(Paths.get("ks"));
  63 
  64         // -genkeypair, and -printcert, -list -alias, -exportcert
  65         // (w/ different formats)
  66         checkGenKeyPair("a", "-keyalg RSA -sigalg MD5withRSA", "MD5withRSA");
  67         checkGenKeyPair("b", "-keyalg RSA -keysize 512", "512-bit RSA key");
  68         checkGenKeyPair("c", "-keyalg RSA", null);
  69 
  70         kt("-list")
  71                 .shouldContain("Warning:")
  72                 .shouldMatch("<a>'s MD5withRSA.*risk")
  73                 .shouldMatch("<b>'s 512-bit RSA key.*risk");
  74         kt("-list -v")
  75                 .shouldContain("Warning:")
  76                 .shouldMatch("<a>'s MD5withRSA.*risk")
  77                 .shouldContain("MD5withRSA (weak)")
  78                 .shouldMatch("<b>'s 512-bit RSA key.*risk")
  79                 .shouldContain("512-bit RSA key (weak)");
  80 
  81         // Multiple warnings for multiple cert in -printcert or -list or -exportcert
  82 
  83         // -certreq, -printcertreq, -gencert
  84         checkCertReq("a", "", null);
  85         gencert("c-a", "")
  86                 .shouldNotContain("Warning"); // new sigalg is not weak
  87         gencert("c-a", "-sigalg MD2withRSA")
  88                 .shouldContain("Warning:")
  89                 .shouldMatch("The output's MD2withRSA.*risk");
  90 
  91         checkCertReq("a", "-sigalg MD5withRSA", "MD5withRSA");
  92         gencert("c-a", "")
  93                 .shouldContain("Warning:")
  94                 .shouldMatch("The input's MD5withRSA.*risk");
  95         gencert("c-a", "-sigalg MD2withRSA")
  96                 .shouldContain("Warning:")
  97                 .shouldMatch("The input's MD5withRSA.*risk")
  98                 .shouldMatch("The output's MD2withRSA.*risk");
  99 
 100         checkCertReq("b", "", "512-bit RSA key");
 101         gencert("c-b", "")
 102                 .shouldContain("Warning:")
 103                 .shouldMatch("The input's 512-bit RSA key.*risk")
 104                 .shouldMatch("The output's 512-bit RSA key.*risk");
 105 
 106         checkCertReq("c", "", null);
 107         gencert("a-c", "")
 108                 .shouldContain("Warning:")
 109                 .shouldMatch("The issuer's MD5withRSA.*risk");
 110 
 111         // but the new cert is not weak
 112         kt("-printcert -file a-c.cert")
 113                 .shouldNotContain("Warning")
 114                 .shouldNotContain("weak");
 115 
 116         gencert("b-c", "")
 117                 .shouldContain("Warning:")
 118                 .shouldMatch("The issuer's 512-bit RSA key.*risk");
 119 
 120         // -importcert
 121         checkImport();
 122 
 123         // -gencrl, -printcrl
 124 
 125         checkGenCRL("a", "", null);
 126         checkGenCRL("a", "-sigalg MD5withRSA", "MD5withRSA");
 127         checkGenCRL("b", "", "512-bit RSA key");
 128         checkGenCRL("c", "", null);
 129 
 130         kt("-delete -alias b");
 131         kt("-printcrl -file b.crl")
 132                 .shouldContain("WARNING: not verified");
 133     }
 134 
 135     static void checkImport() throws Exception {
 136 
 137         saveStore();
 138 
 139         // add trusted cert
 140 
 141         // cert already in
 142         kt("-importcert -alias d -file a.cert", "no")
 143                 .shouldContain("Certificate already exists in keystore")
 144                 .shouldContain("Warning")
 145                 .shouldMatch("MD5withRSA.*risk")
 146                 .shouldContain("Do you still want to add it?");
 147         kt("-importcert -alias d -file a.cert -noprompt")
 148                 .shouldContain("Warning")
 149                 .shouldMatch("MD5withRSA.*risk")
 150                 .shouldNotContain("[no]");
 151 
 152         // cert is self-signed
 153         kt("-delete -alias a");
 154         kt("-delete -alias d");
 155         kt("-importcert -alias d -file a.cert", "no")
 156                 .shouldContain("Warning")
 157                 .shouldContain("MD5withRSA (weak)")
 158                 .shouldMatch("MD5withRSA.*risk")
 159                 .shouldContain("Trust this certificate?");
 160         kt("-importcert -alias d -file a.cert -noprompt")
 161                 .shouldContain("Warning")
 162                 .shouldMatch("MD5withRSA.*risk")
 163                 .shouldNotContain("[no]");
 164 
 165         // cert is self-signed cacerts
 166         String weakSigAlgCA = null;
 167         KeyStore ks = KeyStoreUtil.getCacertsKeyStore();
 168         if (ks != null) {
 169             DisabledAlgorithmConstraints disabledCheck =
 170                     new DisabledAlgorithmConstraints(
 171                             DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
 172             Set<CryptoPrimitive> sigPrimitiveSet = Collections
 173                     .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
 174 
 175             for (String s : Collections.list(ks.aliases())) {
 176                 if (ks.isCertificateEntry(s)) {
 177                     X509Certificate c = (X509Certificate)ks.getCertificate(s);
 178                     String sigAlg = c.getSigAlgName();
 179                     if (!disabledCheck.permits(sigPrimitiveSet, sigAlg, null)) {
 180                         weakSigAlgCA = sigAlg;
 181                         Files.write(Paths.get("ca.cert"),
 182                                 ks.getCertificate(s).getEncoded());
 183                         break;
 184                     }
 185                 }
 186             }
 187         }
 188         if (weakSigAlgCA != null) {
 189             kt("-delete -alias d");
 190             kt("-importcert -alias d -trustcacerts -file ca.cert", "no")
 191                     .shouldContain("Certificate already exists in system-wide CA")
 192                     .shouldContain("Warning")
 193                     .shouldMatch(weakSigAlgCA + ".*risk")
 194                     .shouldContain("Do you still want to add it to your own keystore?");
 195             kt("-importcert -alias d -file ca.cert -noprompt")
 196                     .shouldContain("Warning")
 197                     .shouldMatch(weakSigAlgCA + ".*risk")
 198                     .shouldNotContain("[no]");
 199         }
 200 
 201         // a non self-signed weak cert
 202         reStore();
 203         certreq("b", "");
 204         gencert("c-b", "");
 205         kt("-importcert -alias d -file c-b.cert", "no") // weak but trusted
 206                 .shouldContain("Warning")
 207                 .shouldNotContain("512-bit RSA key (weak)")
 208                 .shouldMatch("The input's 512-bit RSA key.*risk")
 209                 .shouldContain("Trust this certificate?");
 210         kt("-importcert -alias d -file c-b.cert -noprompt")
 211                 .shouldContain("Warning")
 212                 .shouldMatch("512-bit RSA key.*risk")
 213                 .shouldNotContain("[no]");
 214 
 215         kt("-delete -alias b");
 216         kt("-delete -alias c");
 217         kt("-delete -alias d");
 218 
 219         kt("-importcert -alias d -file c-b.cert", "no") // weak and not trusted
 220                 .shouldContain("Warning")
 221                 .shouldContain("512-bit RSA key (weak)")
 222                 .shouldMatch("512-bit RSA key.*risk")
 223                 .shouldContain("Trust this certificate?");
 224         kt("-importcert -alias d -file c-b.cert -noprompt")
 225                 .shouldContain("Warning")
 226                 .shouldMatch("512-bit RSA key.*risk")
 227                 .shouldNotContain("[no]");
 228 
 229         // a non self-signed strong cert
 230         reStore();
 231         certreq("a", "");
 232         gencert("c-a", "");
 233         kt("-importcert -alias d -file c-a.cert") // trusted
 234                 .shouldNotContain("Warning")
 235                 .shouldNotContain("[no]");
 236 
 237         kt("-delete -alias a");
 238         kt("-delete -alias c");
 239         kt("-delete -alias d");
 240 
 241         kt("-importcert -alias d -file c-a.cert", "no") // not trusted
 242                 .shouldNotContain("Warning")
 243                 .shouldContain("Trust this certificate?");
 244         kt("-importcert -alias d -file c-a.cert -noprompt")
 245                 .shouldNotContain("Warning")
 246                 .shouldNotContain("[no]");
 247 
 248         // install reply
 249 
 250         reStore();
 251 
 252         kt("-importcert -alias a -file c-a.cert")
 253                 .shouldNotContain("Warning");
 254 
 255         kt("-importcert -alias b -file c-b.cert", "no")
 256                 .shouldContain("Warning")
 257                 .shouldMatch("The input's 512-bit RSA key.*risk")
 258                 .shouldContain("Trust this certificate?");
 259 
 260         reStore();
 261         gencert("b-a", "");
 262         ByteArrayOutputStream bout = new ByteArrayOutputStream();
 263         bout.write(Files.readAllBytes(Paths.get("b-a.cert")));
 264         bout.write(Files.readAllBytes(Paths.get("c-b.cert")));
 265         Files.write(Paths.get("c-b-a.cert"), bout.toByteArray());
 266 
 267 
 268         kt("-delete -alias b");
 269 
 270         kt("-importcert -alias a -file c-b-a.cert", "no")
 271                 .shouldContain("Warning")
 272                 .shouldMatch("Reply #2 of 2's 512-bit RSA key.*risk")
 273                 .shouldContain("Install reply anyway?");
 274         kt("-importcert -alias a -file c-b-a.cert -noprompt")
 275                 .shouldContain("Warning")
 276                 .shouldMatch("Reply #2 of 2's 512-bit RSA key.*risk")
 277                 .shouldNotContain("[no]");
 278 
 279         kt("-delete -alias c");
 280         kt("-importcert -alias a -file c-b-a.cert", "no")
 281                 .shouldContain("Top-level certificate in reply:")
 282                 .shouldContain("512-bit RSA key (weak)")
 283                 .shouldContain("Warning")
 284                 .shouldMatch("Reply #2 of 2's 512-bit RSA key.*risk")
 285                 .shouldContain("Install reply anyway?");
 286         kt("-importcert -alias a -file c-b-a.cert -noprompt")
 287                 .shouldContain("Warning")
 288                 .shouldMatch("Reply #2 of 2's 512-bit RSA key.*risk")
 289                 .shouldNotContain("[no]");
 290 
 291         reStore();
 292     }
 293 
 294     static void checkGenCRL(String alias, String options, String bad) {
 295 
 296         OutputAnalyzer oa = kt("-gencrl -alias " + alias
 297                 + " -id 1 -file " + alias + ".crl " + options);
 298         if (bad == null) {
 299             oa.shouldNotContain("Warning");
 300         } else {
 301             oa.shouldContain("Warning")
 302                     .shouldMatch(bad + ".*risk");
 303         }
 304 
 305         oa = kt("-printcrl -file " + alias + ".crl");
 306         if (bad == null) {
 307             oa.shouldNotContain("Warning")
 308                     .shouldContain("Verified by " + alias + " in keystore")
 309                     .shouldNotContain("(weak");
 310         } else {
 311             oa.shouldContain("Warning:")
 312                     .shouldMatch(bad + ".*risk")
 313                     .shouldContain("Verified by " + alias + " in keystore")
 314                     .shouldContain(bad + " (weak)");
 315         }
 316     }
 317 
 318     static void checkCertReq(
 319             String alias, String options, String bad) {
 320 
 321         OutputAnalyzer oa = certreq(alias, options);
 322         if (bad == null) {
 323             oa.shouldNotContain("Warning");
 324         } else {
 325             oa.shouldContain("Warning")
 326                     .shouldMatch(bad + ".*risk");
 327         }
 328 
 329         oa = kt("-printcertreq -file " + alias + ".req");
 330         if (bad == null) {
 331             oa.shouldNotContain("Warning")
 332                     .shouldNotContain("(weak)");
 333         } else {
 334             oa.shouldContain("Warning")
 335                     .shouldMatch(bad + ".*risk")
 336                     .shouldContain(bad + " (weak)");
 337         }
 338     }
 339 
 340     static void checkGenKeyPair(
 341             String alias, String options, String bad) {
 342 
 343         OutputAnalyzer oa = genkeypair(alias, options);
 344         if (bad == null) {
 345             oa.shouldNotContain("Warning");
 346         } else {
 347             oa.shouldContain("Warning")
 348                     .shouldMatch(bad + ".*risk");
 349         }
 350 
 351         oa = kt("-exportcert -alias " + alias + " -file " + alias + ".cert");
 352         if (bad == null) {
 353             oa.shouldNotContain("Warning");
 354         } else {
 355             oa.shouldContain("Warning")
 356                     .shouldMatch(bad + ".*risk");
 357         }
 358 
 359         oa = kt("-exportcert -rfc -alias " + alias + " -file " + alias + ".cert");
 360         if (bad == null) {
 361             oa.shouldNotContain("Warning");
 362         } else {
 363             oa.shouldContain("Warning")
 364                     .shouldMatch(bad + ".*risk");
 365         }
 366 
 367         oa = kt("-printcert -rfc -file " + alias + ".cert");
 368         if (bad == null) {
 369             oa.shouldNotContain("Warning");
 370         } else {
 371             oa.shouldContain("Warning")
 372                     .shouldMatch(bad + ".*risk");
 373         }
 374 
 375         oa = kt("-list -alias " + alias);
 376         if (bad == null) {
 377             oa.shouldNotContain("Warning");
 378         } else {
 379             oa.shouldContain("Warning")
 380                     .shouldMatch(bad + ".*risk");
 381         }
 382 
 383         // With cert content
 384 
 385         oa = kt("-printcert -file " + alias + ".cert");
 386         if (bad == null) {
 387             oa.shouldNotContain("Warning");
 388         } else {
 389             oa.shouldContain("Warning")
 390                     .shouldContain(bad + " (weak)")
 391                     .shouldMatch(bad + ".*risk");
 392         }
 393 
 394         oa = kt("-list -v -alias " + alias);
 395         if (bad == null) {
 396             oa.shouldNotContain("Warning");
 397         } else {
 398             oa.shouldContain("Warning")
 399                     .shouldContain(bad + " (weak)")
 400                     .shouldMatch(bad + ".*risk");
 401         }
 402     }
 403 
 404     // This is slow, but real keytool process is launched.
 405     static OutputAnalyzer kt1(String cmd, String... input) {
 406         cmd = "-keystore ks -storepass changeit " +
 407                 "-keypass changeit " + cmd;
 408         System.out.println("---------------------------------------------");
 409         try {
 410             SecurityTools.setResponse(input);
 411             return SecurityTools.keytool(cmd);
 412         } catch (Throwable e) {
 413             throw new RuntimeException(e);
 414         }
 415     }
 416 
 417     // Fast keytool execution by directly calling its main() method
 418     static OutputAnalyzer kt(String cmd, String... input) {
 419         PrintStream out = System.out;
 420         PrintStream err = System.err;
 421         InputStream ins = System.in;
 422         ByteArrayOutputStream bout = new ByteArrayOutputStream();
 423         ByteArrayOutputStream berr = new ByteArrayOutputStream();
 424         boolean succeed = true;
 425         try {
 426             cmd = "-keystore ks -storepass changeit " +
 427                     "-keypass changeit " + cmd;
 428             System.out.println("---------------------------------------------");
 429             System.out.println("$ keytool " + cmd);
 430             System.out.println();
 431             String feed = "";
 432             if (input.length > 0) {
 433                 feed = Stream.of(input).collect(Collectors.joining("\n")) + "\n";
 434             }
 435             System.setIn(new ByteArrayInputStream(feed.getBytes()));
 436             System.setOut(new PrintStream(bout));
 437             System.setErr(new PrintStream(berr));
 438             sun.security.tools.keytool.Main.main(
 439                     cmd.trim().split("\\s+"));
 440         } catch (Exception e) {
 441             // Might be a normal exception when -debug is on or
 442             // SecurityException (thrown by jtreg) when System.exit() is called
 443             if (!(e instanceof SecurityException)) {
 444                 e.printStackTrace();
 445             }
 446             succeed = false;
 447         } finally {
 448             System.setOut(out);
 449             System.setErr(err);
 450             System.setIn(ins);
 451         }
 452         String sout = new String(bout.toByteArray());
 453         String serr = new String(berr.toByteArray());
 454         System.out.println("STDOUT:\n" + sout + "\nSTDERR:\n" + serr);
 455         if (!succeed) {
 456             throw new RuntimeException();
 457         }
 458         return new OutputAnalyzer(sout, serr);
 459     }
 460 
 461     static OutputAnalyzer genkeypair(String alias, String options) {
 462         return kt("-genkeypair -alias " + alias + " -dname CN=" + alias
 463                 + " -keyalg RSA -storetype JKS " + options);
 464     }
 465 
 466     static OutputAnalyzer certreq(String alias, String options) {
 467         return kt("-certreq -alias " + alias
 468                 + " -file " + alias + ".req " + options);
 469     }
 470 
 471     static OutputAnalyzer exportcert(String alias) {
 472         return kt("-exportcert -alias " + alias + " -file " + alias + ".cert");
 473     }
 474 
 475     static OutputAnalyzer gencert(String relation, String options) {
 476         int pos = relation.indexOf("-");
 477         String issuer = relation.substring(0, pos);
 478         String subject = relation.substring(pos + 1);
 479         return kt(" -gencert -alias " + issuer + " -infile " + subject
 480                 + ".req -outfile " + relation + ".cert " + options);
 481     }
 482 
 483     static void saveStore() throws IOException {
 484         System.out.println("---------------------------------------------");
 485         System.out.println("$ cp ks ks2");
 486         Files.copy(Paths.get("ks"), Paths.get("ks2"),
 487                 StandardCopyOption.REPLACE_EXISTING);
 488     }
 489 
 490     static void reStore() throws IOException {
 491         System.out.println("---------------------------------------------");
 492         System.out.println("$ cp ks2 ks");
 493         Files.copy(Paths.get("ks2"), Paths.get("ks"),
 494                 StandardCopyOption.REPLACE_EXISTING);
 495     }
 496 }