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 }