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 import java.io.ByteArrayInputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.net.SocketTimeoutException; 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.security.KeyFactory; 32 import java.security.KeyStore; 33 import java.security.PrivateKey; 34 import java.security.Security; 35 import java.security.cert.Certificate; 36 import java.security.cert.CertificateFactory; 37 import java.security.spec.PKCS8EncodedKeySpec; 38 import java.util.Base64; 39 import java.util.stream.Collectors; 40 41 import javax.net.ssl.KeyManagerFactory; 42 import javax.net.ssl.SSLContext; 43 import javax.net.ssl.SSLHandshakeException; 44 import javax.net.ssl.TrustManagerFactory; 45 46 import jdk.testlibrary.OutputAnalyzer; 47 import jdk.testlibrary.ProcessTools; 48 49 /* 50 * @test 51 * @summary Verify the restrictions for certificate path on JSSE with custom trust store. 52 * @library /lib/testlibrary 53 * @compile JSSEClient.java 54 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions DEFAULT 55 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C1 56 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S1 57 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C2 58 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S2 59 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C3 60 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S3 61 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C4 62 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S4 63 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C5 64 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S5 65 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C6 66 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S6 67 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C7 68 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S7 69 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C8 70 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S8 71 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C9 72 * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S9 73 */ 74 public class TLSRestrictions { 75 76 private static final String TEST_CLASSES = System.getProperty("test.classes"); 77 private static final char[] PASSWORD = "".toCharArray(); 78 private static final String CERT_DIR = System.getProperty("cert.dir", 79 System.getProperty("test.src") + "/certs"); 80 81 static final String PROP = "jdk.certpath.disabledAlgorithms"; 82 static final String NOSHA1 = "MD2, MD5"; 83 private static final String TLSSERVER = "SHA1 usage TLSServer"; 84 private static final String TLSCLIENT = "SHA1 usage TLSClient"; 85 static final String JDKCATLSSERVER = "SHA1 jdkCA & usage TLSServer"; 86 static final String JDKCATLSCLIENT = "SHA1 jdkCA & usage TLSClient"; 87 88 // This is a space holder in command arguments, and stands for none certificate. 89 static final String NONE_CERT = "NONE_CERT"; 90 91 static final String DELIMITER = ","; 92 static final int TIMEOUT = 30000; 93 94 // It checks if java.security contains constraint "SHA1 jdkCA & usage TLSServer" 95 // for jdk.certpath.disabledAlgorithms by default. 96 private static void checkDefaultConstraint() { 97 System.out.println( 98 "Case: Checks the default value of jdk.certpath.disabledAlgorithms"); 99 if (!Security.getProperty(PROP).contains(JDKCATLSSERVER)) { 100 throw new RuntimeException(String.format( 101 "%s doesn't contain constraint \"%s\", the real value is \"%s\".", 102 PROP, JDKCATLSSERVER, Security.getProperty(PROP))); 103 } 104 } 105 106 /* 107 * This method creates trust store and key store with specified certificates 108 * respectively. And then it creates SSL context with the stores. 109 * If trustNames contains NONE_CERT only, it does not create a custom trust 110 * store, but the default one in JDK. 111 * 112 * @param trustNames Trust anchors, which are used to create custom trust store. 113 * If null, no custom trust store is created and the default 114 * trust store in JDK is used. 115 * @param certNames Certificate chain, which is used to create key store. 116 * It cannot be null. 117 */ 118 static SSLContext createSSLContext(String[] trustNames, 119 String[] certNames) throws Exception { 120 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 121 122 TrustManagerFactory tmf = null; 123 if (trustNames != null && trustNames.length > 0 124 && !trustNames[0].equals(NONE_CERT)) { 125 KeyStore trustStore = KeyStore.getInstance("JKS"); 126 trustStore.load(null, null); 127 for (int i = 0; i < trustNames.length; i++) { 128 try (InputStream is = new ByteArrayInputStream( 129 loadCert(trustNames[i]).getBytes())) { 130 Certificate trustCert = certFactory.generateCertificate(is); 131 trustStore.setCertificateEntry("trustCert-" + i, trustCert); 132 } 133 } 134 135 tmf = TrustManagerFactory.getInstance("PKIX"); 136 tmf.init(trustStore); 137 } 138 139 Certificate[] certChain = new Certificate[certNames.length]; 140 for (int i = 0; i < certNames.length; i++) { 141 try (InputStream is = new ByteArrayInputStream( 142 loadCert(certNames[i]).getBytes())) { 143 Certificate cert = certFactory.generateCertificate(is); 144 certChain[i] = cert; 145 } 146 } 147 148 PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec( 149 Base64.getMimeDecoder().decode(loadPrivKey(certNames[0]))); 150 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 151 PrivateKey privKey = keyFactory.generatePrivate(privKeySpec); 152 153 KeyStore keyStore = KeyStore.getInstance("JKS"); 154 keyStore.load(null, null); 155 keyStore.setKeyEntry("keyCert", privKey, PASSWORD, certChain); 156 157 KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509"); 158 kmf.init(keyStore, PASSWORD); 159 160 SSLContext context = SSLContext.getInstance("TLS"); 161 context.init(kmf.getKeyManagers(), 162 tmf == null ? null : tmf.getTrustManagers(), null); 163 return context; 164 } 165 166 /* 167 * This method sets jdk.certpath.disabledAlgorithms, and then retrieves 168 * and prints its value. 169 */ 170 static void setConstraint(String side, String constraint) { 171 System.out.printf("%s: Old %s=%s%n", side, PROP, 172 Security.getProperty(PROP)); 173 Security.setProperty(PROP, constraint); 174 System.out.printf("%s: New %s=%s%n", side, PROP, 175 Security.getProperty(PROP)); 176 } 177 178 /* 179 * This method is used to run a variety of cases. 180 * It launches a server, and then takes a client to connect the server. 181 * Both of server and client use the same certificates. 182 * 183 * @param trustNames Trust anchors, which are used to create custom trust store. 184 * If null, the default trust store in JDK is used. 185 * @param certNames Certificate chain, which is used to create key store. 186 * It cannot be null. The first certificate is regarded as 187 * the end entity. 188 * @param serverConstraint jdk.certpath.disabledAlgorithms value on server side. 189 * @param clientConstraint jdk.certpath.disabledAlgorithms value on client side. 190 * @param needClientAuth If true, server side acquires client authentication; 191 * otherwise, false. 192 * @param pass If true, the connection should be blocked; otherwise, false. 193 */ 194 static void testConstraint(String[] trustNames, String[] certNames, 195 String serverConstraint, String clientConstraint, 196 boolean needClientAuth, boolean pass) throws Throwable { 197 String trustNameStr = trustNames == null ? "" 198 : String.join(DELIMITER, trustNames); 199 String certNameStr = certNames == null ? "" 200 : String.join(DELIMITER, certNames); 201 202 System.out.printf("Case:%n" 203 + " trustNames=%s; certNames=%s%n" 204 + " serverConstraint=%s; clientConstraint=%s%n" 205 + " needClientAuth=%s%n" 206 + " pass=%s%n%n", 207 trustNameStr, certNameStr, 208 serverConstraint, clientConstraint, 209 needClientAuth, 210 pass); 211 setConstraint("Server", serverConstraint); 212 JSSEServer server = new JSSEServer( 213 createSSLContext(trustNames, certNames), 214 needClientAuth); 215 int port = server.getPort(); 216 server.start(); 217 218 // Run client on another JVM so that its properties cannot be in conflict 219 // with server's. 220 OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJvm( 221 "-Dcert.dir=" + CERT_DIR, 222 "-Djava.security.debug=certpath", 223 "-classpath", 224 TEST_CLASSES, 225 "JSSEClient", 226 port + "", 227 trustNameStr, 228 certNameStr, 229 clientConstraint); 230 int exitValue = outputAnalyzer.getExitValue(); 231 String clientOut = outputAnalyzer.getOutput(); 232 233 Exception serverException = server.getException(); 234 if (serverException != null) { 235 System.out.println("Server: failed"); 236 } 237 238 System.out.println("---------- Client output start ----------"); 239 System.out.println(clientOut); 240 System.out.println("---------- Client output end ----------"); 241 242 if (serverException instanceof SocketTimeoutException 243 || clientOut.contains("SocketTimeoutException")) { 244 System.out.println("The communication gets timeout and skips the test."); 245 return; 246 } 247 248 if (pass) { 249 if (serverException != null || exitValue != 0) { 250 throw new RuntimeException( 251 "Unexpected failure. Operation was blocked."); 252 } 253 } else { 254 if (serverException == null && exitValue == 0) { 255 throw new RuntimeException( 256 "Unexpected pass. Operation was allowed."); 257 } 258 259 // The test may encounter non-SSL issues, like network problem. 260 if (!(serverException instanceof SSLHandshakeException 261 || clientOut.contains("SSLHandshakeException"))) { 262 throw new RuntimeException("Failure with unexpected exception."); 263 } 264 } 265 } 266 267 /* 268 * This method is used to run a variety of cases, which don't require client 269 * authentication by default. 270 */ 271 static void testConstraint(String[] trustNames, String[] certNames, 272 String serverConstraint, String clientConstraint, boolean pass) 273 throws Throwable { 274 testConstraint(trustNames, certNames, serverConstraint, clientConstraint, 275 false, pass); 276 } 277 278 public static void main(String[] args) throws Throwable { 279 switch (args[0]) { 280 // Case DEFAULT only checks one of default settings for 281 // jdk.certpath.disabledAlgorithms in JDK/conf/security/java.security. 282 case "DEFAULT": 283 checkDefaultConstraint(); 284 break; 285 286 // Cases C1 and S1 use SHA256 root CA in trust store, 287 // and use SHA256 end entity in key store. 288 // C1 only sets constraint "SHA1 usage TLSServer" on client side; 289 // S1 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 290 // The connection of the both cases should not be blocked. 291 case "C1": 292 testConstraint( 293 new String[] { "ROOT_CA_SHA256" }, 294 new String[] { "INTER_CA_SHA256-ROOT_CA_SHA256" }, 295 NOSHA1, 296 TLSSERVER, 297 true); 298 break; 299 case "S1": 300 testConstraint( 301 new String[] { "ROOT_CA_SHA256" }, 302 new String[] { "INTER_CA_SHA256-ROOT_CA_SHA256" }, 303 TLSCLIENT, 304 NOSHA1, 305 true, 306 true); 307 break; 308 309 // Cases C2 and S2 use SHA256 root CA in trust store, 310 // and use SHA1 end entity in key store. 311 // C2 only sets constraint "SHA1 usage TLSServer" on client side; 312 // S2 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 313 // The connection of the both cases should be blocked. 314 case "C2": 315 testConstraint( 316 new String[] { "ROOT_CA_SHA256" }, 317 new String[] { "INTER_CA_SHA1-ROOT_CA_SHA256" }, 318 NOSHA1, 319 TLSSERVER, 320 false); 321 break; 322 case "S2": 323 testConstraint( 324 new String[] { "ROOT_CA_SHA256" }, 325 new String[] { "INTER_CA_SHA1-ROOT_CA_SHA256" }, 326 TLSCLIENT, 327 NOSHA1, 328 true, 329 false); 330 break; 331 332 // Cases C3 and S3 use SHA1 root CA in trust store, 333 // and use SHA1 end entity in key store. 334 // C3 only sets constraint "SHA1 usage TLSServer" on client side; 335 // S3 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 336 // The connection of the both cases should be blocked. 337 case "C3": 338 testConstraint( 339 new String[] { "ROOT_CA_SHA1" }, 340 new String[] { "INTER_CA_SHA1-ROOT_CA_SHA1" }, 341 NOSHA1, 342 TLSSERVER, 343 false); 344 break; 345 case "S3": 346 testConstraint( 347 new String[] { "ROOT_CA_SHA1" }, 348 new String[] { "INTER_CA_SHA1-ROOT_CA_SHA1" }, 349 TLSCLIENT, 350 NOSHA1, 351 true, 352 false); 353 break; 354 355 // Cases C4 and S4 use SHA1 root CA as trust store, 356 // and use SHA256 end entity in key store. 357 // C4 only sets constraint "SHA1 usage TLSServer" on client side; 358 // S4 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 359 // The connection of the both cases should not be blocked. 360 case "C4": 361 testConstraint( 362 new String[] { "ROOT_CA_SHA1" }, 363 new String[] { "INTER_CA_SHA256-ROOT_CA_SHA1" }, 364 NOSHA1, 365 TLSSERVER, 366 true); 367 break; 368 case "S4": 369 testConstraint( 370 new String[] { "ROOT_CA_SHA1" }, 371 new String[] { "INTER_CA_SHA256-ROOT_CA_SHA1" }, 372 TLSCLIENT, 373 NOSHA1, 374 true, 375 true); 376 break; 377 378 // Cases C5 and S5 use SHA1 root CA in trust store, 379 // and use SHA256 intermediate CA and SHA256 end entity in key store. 380 // C5 only sets constraint "SHA1 usage TLSServer" on client side; 381 // S5 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 382 // The connection of the both cases should not be blocked. 383 case "C5": 384 testConstraint( 385 new String[] { "ROOT_CA_SHA1" }, 386 new String[] { 387 "END_ENTITY_SHA256-INTER_CA_SHA256-ROOT_CA_SHA1", 388 "INTER_CA_SHA256-ROOT_CA_SHA1" }, 389 NOSHA1, 390 TLSSERVER, 391 true); 392 break; 393 case "S5": 394 testConstraint( 395 new String[] { "ROOT_CA_SHA1" }, 396 new String[] { 397 "END_ENTITY_SHA256-INTER_CA_SHA256-ROOT_CA_SHA1", 398 "INTER_CA_SHA256-ROOT_CA_SHA1" }, 399 TLSCLIENT, 400 NOSHA1, 401 true, 402 true); 403 break; 404 405 // Cases C6 and S6 use SHA1 root CA as trust store, 406 // and use SHA1 intermediate CA and SHA256 end entity in key store. 407 // C6 only sets constraint "SHA1 usage TLSServer" on client side; 408 // S6 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 409 // The connection of the both cases should be blocked. 410 case "C6": 411 testConstraint( 412 new String[] { "ROOT_CA_SHA1" }, 413 new String[] { 414 "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA1", 415 "INTER_CA_SHA1-ROOT_CA_SHA1" }, 416 NOSHA1, 417 TLSSERVER, 418 false); 419 break; 420 case "S6": 421 testConstraint( 422 new String[] { "ROOT_CA_SHA1" }, 423 new String[] { 424 "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA1", 425 "INTER_CA_SHA1-ROOT_CA_SHA1" }, 426 TLSCLIENT, 427 NOSHA1, 428 true, 429 false); 430 break; 431 432 // Cases C7 and S7 use SHA256 root CA in trust store, 433 // and use SHA256 intermediate CA and SHA1 end entity in key store. 434 // C7 only sets constraint "SHA1 usage TLSServer" on client side; 435 // S7 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 436 // The connection of the both cases should be blocked. 437 case "C7": 438 testConstraint( 439 new String[] { "ROOT_CA_SHA256" }, 440 new String[] { 441 "END_ENTITY_SHA1-INTER_CA_SHA256-ROOT_CA_SHA256", 442 "INTER_CA_SHA256-ROOT_CA_SHA256" }, 443 NOSHA1, 444 TLSSERVER, 445 false); 446 break; 447 case "S7": 448 testConstraint( 449 new String[] { "ROOT_CA_SHA256" }, 450 new String[] { 451 "END_ENTITY_SHA1-INTER_CA_SHA256-ROOT_CA_SHA256", 452 "INTER_CA_SHA256-ROOT_CA_SHA256" }, 453 TLSCLIENT, 454 NOSHA1, 455 true, 456 false); 457 break; 458 459 // Cases C8 and S8 use SHA256 root CA in trust store, 460 // and use SHA1 intermediate CA and SHA256 end entity in key store. 461 // C8 only sets constraint "SHA1 usage TLSServer" on client side; 462 // S8 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 463 // The connection of the both cases should be blocked. 464 case "C8": 465 testConstraint( 466 new String[] { "ROOT_CA_SHA256" }, 467 new String[] { 468 "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA256", 469 "INTER_CA_SHA1-ROOT_CA_SHA256" }, 470 NOSHA1, 471 TLSSERVER, 472 false); 473 break; 474 case "S8": 475 testConstraint( 476 new String[] { "ROOT_CA_SHA256" }, 477 new String[] { 478 "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA256", 479 "INTER_CA_SHA1-ROOT_CA_SHA256" }, 480 TLSCLIENT, 481 NOSHA1, 482 true, 483 false); 484 break; 485 486 // Cases C9 and S9 use SHA256 root CA and SHA1 intermediate CA in trust store, 487 // and use SHA256 end entity in key store. 488 // C9 only sets constraint "SHA1 usage TLSServer" on client side; 489 // S9 only sets constraint "SHA1 usage TLSClient" on server side with client auth. 490 // The connection of the both cases should not be blocked. 491 case "C9": 492 testConstraint( 493 new String[] { 494 "ROOT_CA_SHA256", 495 "INTER_CA_SHA1-ROOT_CA_SHA256" }, 496 new String[] { 497 "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA256" }, 498 NOSHA1, 499 TLSSERVER, 500 true); 501 break; 502 case "S9": 503 testConstraint( 504 new String[] { 505 "ROOT_CA_SHA256", 506 "INTER_CA_SHA1-ROOT_CA_SHA256" }, 507 new String[] { 508 "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA256" }, 509 TLSCLIENT, 510 NOSHA1, 511 true, 512 true); 513 break; 514 } 515 516 System.out.println("Case passed"); 517 System.out.println("========================================"); 518 } 519 520 private static String loadCert(String certName) { 521 try { 522 Path certFilePath = Paths.get(CERT_DIR, certName + ".cer"); 523 return String.join("\n", 524 Files.lines(certFilePath).filter((String line) -> { 525 return !line.startsWith("Certificate") 526 && !line.startsWith(" "); 527 }).collect(Collectors.toList())); 528 } catch (IOException e) { 529 throw new RuntimeException("Load certificate failed", e); 530 } 531 } 532 533 private static String loadPrivKey(String certName) { 534 Path priveKeyFilePath = Paths.get(CERT_DIR, certName + "-PRIV.key"); 535 try { 536 return new String(Files.readAllBytes(priveKeyFilePath)); 537 } catch (IOException e) { 538 throw new RuntimeException("Load private key failed", e); 539 } 540 } 541 }