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 }