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