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