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 8177569 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 * @build jdk.test.lib.SecurityTools 34 * jdk.test.lib.Utils 35 * jdk.test.lib.Asserts 36 * jdk.test.lib.JDKToolFinder 37 * jdk.test.lib.JDKToolLauncher 38 * jdk.test.lib.Platform 39 * jdk.test.lib.process.* 40 * @run main/othervm/timeout=600 -Duser.language=en -Duser.country=US WeakAlg 41 */ 42 43 import jdk.test.lib.SecurityTools; 44 import jdk.test.lib.process.OutputAnalyzer; 45 import sun.security.tools.KeyStoreUtil; 46 import sun.security.util.DisabledAlgorithmConstraints; 47 48 import java.io.ByteArrayInputStream; 49 import java.io.ByteArrayOutputStream; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.io.PrintStream; 53 import java.nio.file.Files; 54 import java.nio.file.Paths; 55 import java.nio.file.StandardCopyOption; 56 import java.security.CryptoPrimitive; 57 import java.security.KeyStore; 58 import java.security.cert.X509Certificate; 59 import java.util.Collections; 60 import java.util.EnumSet; 61 import java.util.Set; 62 import java.util.stream.Collectors; 63 import java.util.stream.Stream; 64 65 public class WeakAlg { 66 67 public static void main(String[] args) throws Throwable { 68 69 rm("ks"); 70 71 // -genkeypair, and -printcert, -list -alias, -exportcert 72 // (w/ different formats) 73 checkGenKeyPair("a", "-keyalg RSA -sigalg MD5withRSA", "MD5withRSA"); 74 checkGenKeyPair("b", "-keyalg RSA -keysize 512", "512-bit RSA key"); 75 checkGenKeyPair("c", "-keyalg RSA", null); 76 77 kt("-list") 78 .shouldContain("Warning:") 79 .shouldMatch("<a>.*MD5withRSA.*risk") 80 .shouldMatch("<b>.*512-bit RSA key.*risk"); 81 kt("-list -v") 82 .shouldContain("Warning:") 83 .shouldMatch("<a>.*MD5withRSA.*risk") 84 .shouldContain("MD5withRSA (weak)") 85 .shouldMatch("<b>.*512-bit RSA key.*risk") 86 .shouldContain("512-bit RSA key (weak)"); 87 88 // Multiple warnings for multiple cert in -printcert 89 // or -list or -exportcert 90 91 // -certreq, -printcertreq, -gencert 92 checkCertReq("a", "", null); 93 gencert("c-a", "") 94 .shouldNotContain("Warning"); // new sigalg is not weak 95 gencert("c-a", "-sigalg MD2withRSA") 96 .shouldContain("Warning:") 97 .shouldMatch("The generated certificate.*MD2withRSA.*risk"); 98 99 checkCertReq("a", "-sigalg MD5withRSA", "MD5withRSA"); 100 gencert("c-a", "") 101 .shouldContain("Warning:") 102 .shouldMatch("The certificate request.*MD5withRSA.*risk"); 103 gencert("c-a", "-sigalg MD2withRSA") 104 .shouldContain("Warning:") 105 .shouldMatch("The certificate request.*MD5withRSA.*risk") 106 .shouldMatch("The generated certificate.*MD2withRSA.*risk"); 107 108 checkCertReq("b", "", "512-bit RSA key"); 109 gencert("c-b", "") 110 .shouldContain("Warning:") 111 .shouldMatch("The certificate request.*512-bit RSA key.*risk") 112 .shouldMatch("The generated certificate.*512-bit RSA key.*risk"); 113 114 checkCertReq("c", "", null); 115 gencert("a-c", "") 116 .shouldContain("Warning:") 117 .shouldMatch("The issuer.*MD5withRSA.*risk"); 118 119 // but the new cert is not weak 120 kt("-printcert -file a-c.cert") 121 .shouldNotContain("Warning") 122 .shouldNotContain("weak"); 123 124 gencert("b-c", "") 125 .shouldContain("Warning:") 126 .shouldMatch("The issuer.*512-bit RSA key.*risk"); 127 128 // -importcert 129 checkImport(); 130 131 // -importkeystore 132 checkImportKeyStore(); 133 134 // -gencrl, -printcrl 135 136 checkGenCRL("a", "", null); 137 checkGenCRL("a", "-sigalg MD5withRSA", "MD5withRSA"); 138 checkGenCRL("b", "", "512-bit RSA key"); 139 checkGenCRL("c", "", null); 140 141 kt("-delete -alias b"); 142 kt("-printcrl -file b.crl") 143 .shouldContain("WARNING: not verified"); 144 } 145 146 static void checkImportKeyStore() throws Exception { 147 148 saveStore(); 149 150 rm("ks"); 151 kt("-importkeystore -srckeystore ks2 -srcstorepass changeit") 152 .shouldContain("3 entries successfully imported") 153 .shouldContain("Warning") 154 .shouldMatch("<b>.*512-bit RSA key.*risk") 155 .shouldMatch("<a>.*MD5withRSA.*risk"); 156 157 rm("ks"); 158 kt("-importkeystore -srckeystore ks2 -srcstorepass changeit -srcalias a") 159 .shouldContain("Warning") 160 .shouldMatch("<a>.*MD5withRSA.*risk"); 161 162 reStore(); 163 } 164 165 static void checkImport() throws Exception { 166 167 saveStore(); 168 169 // add trusted cert 170 171 // cert already in 172 kt("-importcert -alias d -file a.cert", "no") 173 .shouldContain("Certificate already exists in keystore") 174 .shouldContain("Warning") 175 .shouldMatch("The input.*MD5withRSA.*risk") 176 .shouldContain("Do you still want to add it?"); 177 kt("-importcert -alias d -file a.cert -noprompt") 178 .shouldContain("Warning") 179 .shouldMatch("The input.*MD5withRSA.*risk") 180 .shouldNotContain("[no]"); 181 182 // cert is self-signed 183 kt("-delete -alias a"); 184 kt("-delete -alias d"); 185 kt("-importcert -alias d -file a.cert", "no") 186 .shouldContain("Warning") 187 .shouldContain("MD5withRSA (weak)") 188 .shouldMatch("The input.*MD5withRSA.*risk") 189 .shouldContain("Trust this certificate?"); 190 kt("-importcert -alias d -file a.cert -noprompt") 191 .shouldContain("Warning") 192 .shouldMatch("The input.*MD5withRSA.*risk") 193 .shouldNotContain("[no]"); 194 195 // JDK-8177569: no warning for sigalg of trusted cert 196 String weakSigAlgCA = null; 197 KeyStore ks = KeyStoreUtil.getCacertsKeyStore(); 198 if (ks != null) { 199 DisabledAlgorithmConstraints disabledCheck = 200 new DisabledAlgorithmConstraints( 201 DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS); 202 Set<CryptoPrimitive> sigPrimitiveSet = Collections 203 .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE)); 204 205 for (String s : Collections.list(ks.aliases())) { 206 if (ks.isCertificateEntry(s)) { 207 X509Certificate c = (X509Certificate)ks.getCertificate(s); 208 String sigAlg = c.getSigAlgName(); 209 if (!disabledCheck.permits(sigPrimitiveSet, sigAlg, null)) { 210 weakSigAlgCA = sigAlg; 211 Files.write(Paths.get("ca.cert"), 212 ks.getCertificate(s).getEncoded()); 213 break; 214 } 215 } 216 } 217 } 218 if (weakSigAlgCA != null) { 219 // The following 2 commands still have a warning on why not using 220 // the -cacerts option directly. 221 kt("-list -keystore " + KeyStoreUtil.getCacerts()) 222 .shouldNotContain("risk"); 223 kt("-list -v -keystore " + KeyStoreUtil.getCacerts()) 224 .shouldNotContain("risk"); 225 226 // -printcert will always show warnings 227 kt("-printcert -file ca.cert") 228 .shouldContain("name: " + weakSigAlgCA + " (weak)") 229 .shouldContain("Warning") 230 .shouldMatch("The certificate.*" + weakSigAlgCA + ".*risk"); 231 kt("-printcert -file ca.cert -trustcacerts") // -trustcacerts useless 232 .shouldContain("name: " + weakSigAlgCA + " (weak)") 233 .shouldContain("Warning") 234 .shouldMatch("The certificate.*" + weakSigAlgCA + ".*risk"); 235 236 // Importing with -trustcacerts ignore CA cert's sig alg 237 kt("-delete -alias d"); 238 kt("-importcert -alias d -trustcacerts -file ca.cert", "no") 239 .shouldContain("Certificate already exists in system-wide CA") 240 .shouldNotContain("risk") 241 .shouldContain("Do you still want to add it to your own keystore?"); 242 kt("-importcert -alias d -trustcacerts -file ca.cert -noprompt") 243 .shouldNotContain("risk") 244 .shouldNotContain("[no]"); 245 246 // but not without -trustcacerts 247 kt("-delete -alias d"); 248 kt("-importcert -alias d -file ca.cert", "no") 249 .shouldContain("name: " + weakSigAlgCA + " (weak)") 250 .shouldContain("Warning") 251 .shouldMatch("The input.*" + weakSigAlgCA + ".*risk") 252 .shouldContain("Trust this certificate?"); 253 kt("-importcert -alias d -file ca.cert -noprompt") 254 .shouldContain("Warning") 255 .shouldMatch("The input.*" + weakSigAlgCA + ".*risk") 256 .shouldNotContain("[no]"); 257 } 258 259 // a non self-signed weak cert 260 reStore(); 261 certreq("b", ""); 262 gencert("c-b", ""); 263 kt("-importcert -alias d -file c-b.cert") // weak only, no prompt 264 .shouldContain("Warning") 265 .shouldNotContain("512-bit RSA key (weak)") 266 .shouldMatch("The input.*512-bit RSA key.*risk") 267 .shouldNotContain("[no]"); 268 269 kt("-delete -alias b"); 270 kt("-delete -alias c"); 271 kt("-delete -alias d"); 272 273 kt("-importcert -alias d -file c-b.cert", "no") // weak and not trusted 274 .shouldContain("Warning") 275 .shouldContain("512-bit RSA key (weak)") 276 .shouldMatch("The input.*512-bit RSA key.*risk") 277 .shouldContain("Trust this certificate?"); 278 kt("-importcert -alias d -file c-b.cert -noprompt") 279 .shouldContain("Warning") 280 .shouldMatch("The input.*512-bit RSA key.*risk") 281 .shouldNotContain("[no]"); 282 283 // a non self-signed strong cert 284 reStore(); 285 certreq("a", ""); 286 gencert("c-a", ""); 287 kt("-importcert -alias d -file c-a.cert") // trusted 288 .shouldNotContain("Warning") 289 .shouldNotContain("[no]"); 290 291 kt("-delete -alias a"); 292 kt("-delete -alias c"); 293 kt("-delete -alias d"); 294 295 kt("-importcert -alias d -file c-a.cert", "no") // not trusted 296 .shouldNotContain("Warning") 297 .shouldContain("Trust this certificate?"); 298 kt("-importcert -alias d -file c-a.cert -noprompt") 299 .shouldNotContain("Warning") 300 .shouldNotContain("[no]"); 301 302 // install reply 303 304 reStore(); 305 certreq("c", ""); 306 gencert("a-c", ""); 307 kt("-importcert -alias c -file a-c.cert") 308 .shouldContain("Warning") 309 .shouldMatch("Issuer <a>.*MD5withRSA.*risk"); 310 311 // JDK-8177569: no warning for sigalg of trusted cert 312 reStore(); 313 // Change a into a TrustedCertEntry 314 kt("-exportcert -alias a -file a.cert"); 315 kt("-delete -alias a"); 316 kt("-importcert -alias a -file a.cert -noprompt"); 317 kt("-list -alias a -v") 318 .shouldNotContain("weak") 319 .shouldNotContain("Warning"); 320 // This time a is trusted and no warning on its weak sig alg 321 kt("-importcert -alias c -file a-c.cert") 322 .shouldNotContain("Warning"); 323 324 reStore(); 325 326 gencert("a-b", ""); 327 gencert("b-c", ""); 328 329 // Full chain with root 330 cat("a-a-b-c.cert", "b-c.cert", "a-b.cert", "a.cert"); 331 kt("-importcert -alias c -file a-a-b-c.cert") // only weak 332 .shouldContain("Warning") 333 .shouldMatch("Reply #2 of 3.*512-bit RSA key.*risk") 334 .shouldMatch("Reply #3 of 3.*MD5withRSA.*risk") 335 .shouldNotContain("[no]"); 336 337 // Without root 338 cat("a-b-c.cert", "b-c.cert", "a-b.cert"); 339 kt("-importcert -alias c -file a-b-c.cert") // only weak 340 .shouldContain("Warning") 341 .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk") 342 .shouldMatch("Issuer <a>.*MD5withRSA.*risk") 343 .shouldNotContain("[no]"); 344 345 reStore(); 346 gencert("b-a", ""); 347 348 kt("-importcert -alias a -file b-a.cert") 349 .shouldContain("Warning") 350 .shouldMatch("Issuer <b>.*512-bit RSA key.*risk") 351 .shouldNotContain("[no]"); 352 353 kt("-importcert -alias a -file c-a.cert") 354 .shouldNotContain("Warning"); 355 356 kt("-importcert -alias b -file c-b.cert") 357 .shouldContain("Warning") 358 .shouldMatch("The input.*512-bit RSA key.*risk") 359 .shouldNotContain("[no]"); 360 361 reStore(); 362 gencert("b-a", ""); 363 364 cat("c-b-a.cert", "b-a.cert", "c-b.cert"); 365 366 kt("-printcert -file c-b-a.cert") 367 .shouldContain("Warning") 368 .shouldMatch("The certificate #2 of 2.*512-bit RSA key.*risk"); 369 370 kt("-delete -alias b"); 371 372 kt("-importcert -alias a -file c-b-a.cert") 373 .shouldContain("Warning") 374 .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk") 375 .shouldNotContain("[no]"); 376 377 kt("-delete -alias c"); 378 kt("-importcert -alias a -file c-b-a.cert", "no") 379 .shouldContain("Top-level certificate in reply:") 380 .shouldContain("512-bit RSA key (weak)") 381 .shouldContain("Warning") 382 .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk") 383 .shouldContain("Install reply anyway?"); 384 kt("-importcert -alias a -file c-b-a.cert -noprompt") 385 .shouldContain("Warning") 386 .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk") 387 .shouldNotContain("[no]"); 388 389 reStore(); 390 } 391 392 private static void cat(String dest, String... src) throws IOException { 393 System.out.println("---------------------------------------------"); 394 System.out.printf("$ cat "); 395 396 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 397 for (String s : src) { 398 System.out.printf(s + " "); 399 bout.write(Files.readAllBytes(Paths.get(s))); 400 } 401 Files.write(Paths.get(dest), bout.toByteArray()); 402 System.out.println("> " + dest); 403 } 404 405 static void checkGenCRL(String alias, String options, String bad) { 406 407 OutputAnalyzer oa = kt("-gencrl -alias " + alias 408 + " -id 1 -file " + alias + ".crl " + options); 409 if (bad == null) { 410 oa.shouldNotContain("Warning"); 411 } else { 412 oa.shouldContain("Warning") 413 .shouldMatch("The generated CRL.*" + bad + ".*risk"); 414 } 415 416 oa = kt("-printcrl -file " + alias + ".crl"); 417 if (bad == null) { 418 oa.shouldNotContain("Warning") 419 .shouldContain("Verified by " + alias + " in keystore") 420 .shouldNotContain("(weak"); 421 } else { 422 oa.shouldContain("Warning:") 423 .shouldMatch("The CRL.*" + bad + ".*risk") 424 .shouldContain("Verified by " + alias + " in keystore") 425 .shouldContain(bad + " (weak)"); 426 } 427 } 428 429 static void checkCertReq( 430 String alias, String options, String bad) { 431 432 OutputAnalyzer oa = certreq(alias, options); 433 if (bad == null) { 434 oa.shouldNotContain("Warning"); 435 } else { 436 oa.shouldContain("Warning") 437 .shouldMatch("The generated certificate request.*" + bad + ".*risk"); 438 } 439 440 oa = kt("-printcertreq -file " + alias + ".req"); 441 if (bad == null) { 442 oa.shouldNotContain("Warning") 443 .shouldNotContain("(weak)"); 444 } else { 445 oa.shouldContain("Warning") 446 .shouldMatch("The certificate request.*" + bad + ".*risk") 447 .shouldContain(bad + " (weak)"); 448 } 449 } 450 451 static void checkGenKeyPair( 452 String alias, String options, String bad) { 453 454 OutputAnalyzer oa = genkeypair(alias, options); 455 if (bad == null) { 456 oa.shouldNotContain("Warning"); 457 } else { 458 oa.shouldContain("Warning") 459 .shouldMatch("The generated certificate.*" + bad + ".*risk"); 460 } 461 462 oa = kt("-exportcert -alias " + alias + " -file " + alias + ".cert"); 463 if (bad == null) { 464 oa.shouldNotContain("Warning"); 465 } else { 466 oa.shouldContain("Warning") 467 .shouldMatch("The certificate.*" + bad + ".*risk"); 468 } 469 470 oa = kt("-exportcert -rfc -alias " + alias + " -file " + alias + ".cert"); 471 if (bad == null) { 472 oa.shouldNotContain("Warning"); 473 } else { 474 oa.shouldContain("Warning") 475 .shouldMatch("The certificate.*" + bad + ".*risk"); 476 } 477 478 oa = kt("-printcert -rfc -file " + alias + ".cert"); 479 if (bad == null) { 480 oa.shouldNotContain("Warning"); 481 } else { 482 oa.shouldContain("Warning") 483 .shouldMatch("The certificate.*" + bad + ".*risk"); 484 } 485 486 oa = kt("-list -alias " + alias); 487 if (bad == null) { 488 oa.shouldNotContain("Warning"); 489 } else { 490 oa.shouldContain("Warning") 491 .shouldMatch("The certificate.*" + bad + ".*risk"); 492 } 493 494 // With cert content 495 496 oa = kt("-printcert -file " + alias + ".cert"); 497 if (bad == null) { 498 oa.shouldNotContain("Warning"); 499 } else { 500 oa.shouldContain("Warning") 501 .shouldContain(bad + " (weak)") 502 .shouldMatch("The certificate.*" + bad + ".*risk"); 503 } 504 505 oa = kt("-list -v -alias " + alias); 506 if (bad == null) { 507 oa.shouldNotContain("Warning"); 508 } else { 509 oa.shouldContain("Warning") 510 .shouldContain(bad + " (weak)") 511 .shouldMatch("The certificate.*" + bad + ".*risk"); 512 } 513 } 514 515 // This is slow, but real keytool process is launched. 516 static OutputAnalyzer kt1(String cmd, String... input) { 517 cmd = "-keystore ks -storepass changeit " + 518 "-keypass changeit " + cmd; 519 System.out.println("---------------------------------------------"); 520 try { 521 SecurityTools.setResponse(input); 522 return SecurityTools.keytool(cmd); 523 } catch (Throwable e) { 524 throw new RuntimeException(e); 525 } 526 } 527 528 // Fast keytool execution by directly calling its main() method 529 static OutputAnalyzer kt(String cmd, String... input) { 530 PrintStream out = System.out; 531 PrintStream err = System.err; 532 InputStream ins = System.in; 533 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 534 ByteArrayOutputStream berr = new ByteArrayOutputStream(); 535 boolean succeed = true; 536 try { 537 cmd = "-keystore ks -storepass changeit " + 538 "-keypass changeit " + cmd; 539 System.out.println("---------------------------------------------"); 540 System.out.println("$ keytool " + cmd); 541 System.out.println(); 542 String feed = ""; 543 if (input.length > 0) { 544 feed = Stream.of(input).collect(Collectors.joining("\n")) + "\n"; 545 } 546 System.setIn(new ByteArrayInputStream(feed.getBytes())); 547 System.setOut(new PrintStream(bout)); 548 System.setErr(new PrintStream(berr)); 549 sun.security.tools.keytool.Main.main( 550 cmd.trim().split("\\s+")); 551 } catch (Exception e) { 552 // Might be a normal exception when -debug is on or 553 // SecurityException (thrown by jtreg) when System.exit() is called 554 if (!(e instanceof SecurityException)) { 555 e.printStackTrace(); 556 } 557 succeed = false; 558 } finally { 559 System.setOut(out); 560 System.setErr(err); 561 System.setIn(ins); 562 } 563 String sout = new String(bout.toByteArray()); 564 String serr = new String(berr.toByteArray()); 565 System.out.println("STDOUT:\n" + sout + "\nSTDERR:\n" + serr); 566 if (!succeed) { 567 throw new RuntimeException(); 568 } 569 return new OutputAnalyzer(sout, serr); 570 } 571 572 static OutputAnalyzer genkeypair(String alias, String options) { 573 return kt("-genkeypair -alias " + alias + " -dname CN=" + alias 574 + " -keyalg RSA -storetype JKS " + options); 575 } 576 577 static OutputAnalyzer certreq(String alias, String options) { 578 return kt("-certreq -alias " + alias 579 + " -file " + alias + ".req " + options); 580 } 581 582 static OutputAnalyzer exportcert(String alias) { 583 return kt("-exportcert -alias " + alias + " -file " + alias + ".cert"); 584 } 585 586 static OutputAnalyzer gencert(String relation, String options) { 587 int pos = relation.indexOf("-"); 588 String issuer = relation.substring(0, pos); 589 String subject = relation.substring(pos + 1); 590 return kt(" -gencert -alias " + issuer + " -infile " + subject 591 + ".req -outfile " + relation + ".cert " + options); 592 } 593 594 static void saveStore() throws IOException { 595 System.out.println("---------------------------------------------"); 596 System.out.println("$ cp ks ks2"); 597 Files.copy(Paths.get("ks"), Paths.get("ks2"), 598 StandardCopyOption.REPLACE_EXISTING); 599 } 600 601 static void reStore() throws IOException { 602 System.out.println("---------------------------------------------"); 603 System.out.println("$ cp ks2 ks"); 604 Files.copy(Paths.get("ks2"), Paths.get("ks"), 605 StandardCopyOption.REPLACE_EXISTING); 606 } 607 608 static void rm(String s) throws IOException { 609 System.out.println("---------------------------------------------"); 610 System.out.println("$ rm " + s); 611 Files.deleteIfExists(Paths.get(s)); 612 } 613 }