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