| /* |
| * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.SocketTimeoutException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.security.KeyFactory; |
| import java.security.KeyStore; |
| import java.security.PrivateKey; |
| import java.security.Security; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateFactory; |
| import java.security.spec.PKCS8EncodedKeySpec; |
| import java.util.Base64; |
| import java.util.stream.Collectors; |
| |
| import javax.net.ssl.KeyManagerFactory; |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.SSLHandshakeException; |
| import javax.net.ssl.TrustManagerFactory; |
| |
| import jdk.test.lib.process.OutputAnalyzer; |
| import jdk.test.lib.process.ProcessTools; |
| |
| /* |
| * @test |
| * @bug 8165367 |
| * @summary Verify the restrictions for certificate path on JSSE with custom trust store. |
| * @library /test/lib |
| * @build jdk.test.lib.Utils |
| * jdk.test.lib.Asserts |
| * jdk.test.lib.JDKToolFinder |
| * jdk.test.lib.JDKToolLauncher |
| * jdk.test.lib.Platform |
| * jdk.test.lib.process.* |
| * @compile JSSEClient.java |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions DEFAULT |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C1 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S1 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C2 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S2 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C3 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S3 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C4 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S4 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C5 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S5 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C6 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S6 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C7 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S7 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C8 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S8 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions C9 |
| * @run main/othervm -Djava.security.debug=certpath TLSRestrictions S9 |
| */ |
| public class TLSRestrictions { |
| |
| private static final String TEST_CLASSES = System.getProperty("test.classes"); |
| private static final char[] PASSWORD = "".toCharArray(); |
| private static final String CERT_DIR = System.getProperty("cert.dir", |
| System.getProperty("test.src") + "/certs"); |
| |
| static final String PROP = "jdk.certpath.disabledAlgorithms"; |
| static final String NOSHA1 = "MD2, MD5"; |
| private static final String TLSSERVER = "SHA1 usage TLSServer"; |
| private static final String TLSCLIENT = "SHA1 usage TLSClient"; |
| static final String JDKCATLSSERVER = "SHA1 jdkCA & usage TLSServer"; |
| static final String JDKCATLSCLIENT = "SHA1 jdkCA & usage TLSClient"; |
| |
| // This is a space holder in command arguments, and stands for none certificate. |
| static final String NONE_CERT = "NONE_CERT"; |
| |
| static final String DELIMITER = ","; |
| static final int TIMEOUT = 30000; |
| |
| // It checks if java.security contains constraint "SHA1 jdkCA & usage TLSServer" |
| // for jdk.certpath.disabledAlgorithms by default. |
| private static void checkDefaultConstraint() { |
| System.out.println( |
| "Case: Checks the default value of jdk.certpath.disabledAlgorithms"); |
| if (!Security.getProperty(PROP).contains(JDKCATLSSERVER)) { |
| throw new RuntimeException(String.format( |
| "%s doesn't contain constraint \"%s\", the real value is \"%s\".", |
| PROP, JDKCATLSSERVER, Security.getProperty(PROP))); |
| } |
| } |
| |
| /* |
| * This method creates trust store and key store with specified certificates |
| * respectively. And then it creates SSL context with the stores. |
| * If trustNames contains NONE_CERT only, it does not create a custom trust |
| * store, but the default one in JDK. |
| * |
| * @param trustNames Trust anchors, which are used to create custom trust store. |
| * If null, no custom trust store is created and the default |
| * trust store in JDK is used. |
| * @param certNames Certificate chain, which is used to create key store. |
| * It cannot be null. |
| */ |
| static SSLContext createSSLContext(String[] trustNames, |
| String[] certNames) throws Exception { |
| CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); |
| |
| TrustManagerFactory tmf = null; |
| if (trustNames != null && trustNames.length > 0 |
| && !trustNames[0].equals(NONE_CERT)) { |
| KeyStore trustStore = KeyStore.getInstance("JKS"); |
| trustStore.load(null, null); |
| for (int i = 0; i < trustNames.length; i++) { |
| try (InputStream is = new ByteArrayInputStream( |
| loadCert(trustNames[i]).getBytes())) { |
| Certificate trustCert = certFactory.generateCertificate(is); |
| trustStore.setCertificateEntry("trustCert-" + i, trustCert); |
| } |
| } |
| |
| tmf = TrustManagerFactory.getInstance("PKIX"); |
| tmf.init(trustStore); |
| } |
| |
| Certificate[] certChain = new Certificate[certNames.length]; |
| for (int i = 0; i < certNames.length; i++) { |
| try (InputStream is = new ByteArrayInputStream( |
| loadCert(certNames[i]).getBytes())) { |
| Certificate cert = certFactory.generateCertificate(is); |
| certChain[i] = cert; |
| } |
| } |
| |
| PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec( |
| Base64.getMimeDecoder().decode(loadPrivKey(certNames[0]))); |
| KeyFactory keyFactory = KeyFactory.getInstance("RSA"); |
| PrivateKey privKey = keyFactory.generatePrivate(privKeySpec); |
| |
| KeyStore keyStore = KeyStore.getInstance("JKS"); |
| keyStore.load(null, null); |
| keyStore.setKeyEntry("keyCert", privKey, PASSWORD, certChain); |
| |
| KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509"); |
| kmf.init(keyStore, PASSWORD); |
| |
| SSLContext context = SSLContext.getInstance("TLS"); |
| context.init(kmf.getKeyManagers(), |
| tmf == null ? null : tmf.getTrustManagers(), null); |
| return context; |
| } |
| |
| /* |
| * This method sets jdk.certpath.disabledAlgorithms, and then retrieves |
| * and prints its value. |
| */ |
| static void setConstraint(String side, String constraint) { |
| System.out.printf("%s: Old %s=%s%n", side, PROP, |
| Security.getProperty(PROP)); |
| Security.setProperty(PROP, constraint); |
| System.out.printf("%s: New %s=%s%n", side, PROP, |
| Security.getProperty(PROP)); |
| } |
| |
| /* |
| * This method is used to run a variety of cases. |
| * It launches a server, and then takes a client to connect the server. |
| * Both of server and client use the same certificates. |
| * |
| * @param trustNames Trust anchors, which are used to create custom trust store. |
| * If null, the default trust store in JDK is used. |
| * @param certNames Certificate chain, which is used to create key store. |
| * It cannot be null. The first certificate is regarded as |
| * the end entity. |
| * @param serverConstraint jdk.certpath.disabledAlgorithms value on server side. |
| * @param clientConstraint jdk.certpath.disabledAlgorithms value on client side. |
| * @param needClientAuth If true, server side acquires client authentication; |
| * otherwise, false. |
| * @param pass If true, the connection should be blocked; otherwise, false. |
| */ |
| static void testConstraint(String[] trustNames, String[] certNames, |
| String serverConstraint, String clientConstraint, |
| boolean needClientAuth, boolean pass) throws Exception { |
| String trustNameStr = trustNames == null ? "" |
| : String.join(DELIMITER, trustNames); |
| String certNameStr = certNames == null ? "" |
| : String.join(DELIMITER, certNames); |
| |
| System.out.printf("Case:%n" |
| + " trustNames=%s; certNames=%s%n" |
| + " serverConstraint=%s; clientConstraint=%s%n" |
| + " needClientAuth=%s%n" |
| + " pass=%s%n%n", |
| trustNameStr, certNameStr, |
| serverConstraint, clientConstraint, |
| needClientAuth, |
| pass); |
| |
| JSSEServer server = new JSSEServer( |
| createSSLContext(trustNames, certNames), |
| serverConstraint, |
| needClientAuth); |
| int port = server.getPort(); |
| server.start(); |
| |
| // Run client on another JVM so that its properties cannot be in conflict |
| // with server's. |
| OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJvm( |
| "-Dcert.dir=" + CERT_DIR, |
| "-Djava.security.debug=certpath", |
| "-classpath", |
| TEST_CLASSES, |
| "JSSEClient", |
| port + "", |
| trustNameStr, |
| certNameStr, |
| clientConstraint); |
| int exitValue = outputAnalyzer.getExitValue(); |
| String clientOut = outputAnalyzer.getOutput(); |
| |
| Exception serverException = server.getException(); |
| if (serverException != null) { |
| System.out.println("Server: failed"); |
| } |
| |
| System.out.println("---------- Client output start ----------"); |
| System.out.println(clientOut); |
| System.out.println("---------- Client output end ----------"); |
| |
| if (serverException instanceof SocketTimeoutException |
| || clientOut.contains("SocketTimeoutException")) { |
| System.out.println("The communication gets timeout and skips the test."); |
| return; |
| } |
| |
| if (pass) { |
| if (serverException != null || exitValue != 0) { |
| throw new RuntimeException( |
| "Unexpected failure. Operation was blocked."); |
| } |
| } else { |
| if (serverException == null && exitValue == 0) { |
| throw new RuntimeException( |
| "Unexpected pass. Operation was allowed."); |
| } |
| |
| // The test may encounter non-SSL issues, like network problem. |
| if (!(serverException instanceof SSLHandshakeException |
| || clientOut.contains("SSLHandshakeException"))) { |
| throw new RuntimeException("Failure with unexpected exception."); |
| } |
| } |
| } |
| |
| /* |
| * This method is used to run a variety of cases, which don't require client |
| * authentication by default. |
| */ |
| static void testConstraint(String[] trustNames, String[] certNames, |
| String serverConstraint, String clientConstraint, boolean pass) |
| throws Exception { |
| testConstraint(trustNames, certNames, serverConstraint, clientConstraint, |
| false, pass); |
| } |
| |
| public static void main(String[] args) throws Exception { |
| switch (args[0]) { |
| // Case DEFAULT only checks one of default settings for |
| // jdk.certpath.disabledAlgorithms in JDK/conf/security/java.security. |
| case "DEFAULT": |
| checkDefaultConstraint(); |
| break; |
| |
| // Cases C1 and S1 use SHA256 root CA in trust store, |
| // and use SHA256 end entity in key store. |
| // C1 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S1 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should not be blocked. |
| case "C1": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA256" }, |
| new String[] { "INTER_CA_SHA256-ROOT_CA_SHA256" }, |
| NOSHA1, |
| TLSSERVER, |
| true); |
| break; |
| case "S1": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA256" }, |
| new String[] { "INTER_CA_SHA256-ROOT_CA_SHA256" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| true); |
| break; |
| |
| // Cases C2 and S2 use SHA256 root CA in trust store, |
| // and use SHA1 end entity in key store. |
| // C2 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S2 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should be blocked. |
| case "C2": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA256" }, |
| new String[] { "INTER_CA_SHA1-ROOT_CA_SHA256" }, |
| NOSHA1, |
| TLSSERVER, |
| false); |
| break; |
| case "S2": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA256" }, |
| new String[] { "INTER_CA_SHA1-ROOT_CA_SHA256" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| false); |
| break; |
| |
| // Cases C3 and S3 use SHA1 root CA in trust store, |
| // and use SHA1 end entity in key store. |
| // C3 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S3 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should be blocked. |
| case "C3": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA1" }, |
| new String[] { "INTER_CA_SHA1-ROOT_CA_SHA1" }, |
| NOSHA1, |
| TLSSERVER, |
| false); |
| break; |
| case "S3": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA1" }, |
| new String[] { "INTER_CA_SHA1-ROOT_CA_SHA1" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| false); |
| break; |
| |
| // Cases C4 and S4 use SHA1 root CA as trust store, |
| // and use SHA256 end entity in key store. |
| // C4 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S4 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should not be blocked. |
| case "C4": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA1" }, |
| new String[] { "INTER_CA_SHA256-ROOT_CA_SHA1" }, |
| NOSHA1, |
| TLSSERVER, |
| true); |
| break; |
| case "S4": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA1" }, |
| new String[] { "INTER_CA_SHA256-ROOT_CA_SHA1" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| true); |
| break; |
| |
| // Cases C5 and S5 use SHA1 root CA in trust store, |
| // and use SHA256 intermediate CA and SHA256 end entity in key store. |
| // C5 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S5 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should not be blocked. |
| case "C5": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA1" }, |
| new String[] { |
| "END_ENTITY_SHA256-INTER_CA_SHA256-ROOT_CA_SHA1", |
| "INTER_CA_SHA256-ROOT_CA_SHA1" }, |
| NOSHA1, |
| TLSSERVER, |
| true); |
| break; |
| case "S5": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA1" }, |
| new String[] { |
| "END_ENTITY_SHA256-INTER_CA_SHA256-ROOT_CA_SHA1", |
| "INTER_CA_SHA256-ROOT_CA_SHA1" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| true); |
| break; |
| |
| // Cases C6 and S6 use SHA1 root CA as trust store, |
| // and use SHA1 intermediate CA and SHA256 end entity in key store. |
| // C6 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S6 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should be blocked. |
| case "C6": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA1" }, |
| new String[] { |
| "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA1", |
| "INTER_CA_SHA1-ROOT_CA_SHA1" }, |
| NOSHA1, |
| TLSSERVER, |
| false); |
| break; |
| case "S6": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA1" }, |
| new String[] { |
| "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA1", |
| "INTER_CA_SHA1-ROOT_CA_SHA1" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| false); |
| break; |
| |
| // Cases C7 and S7 use SHA256 root CA in trust store, |
| // and use SHA256 intermediate CA and SHA1 end entity in key store. |
| // C7 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S7 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should be blocked. |
| case "C7": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA256" }, |
| new String[] { |
| "END_ENTITY_SHA1-INTER_CA_SHA256-ROOT_CA_SHA256", |
| "INTER_CA_SHA256-ROOT_CA_SHA256" }, |
| NOSHA1, |
| TLSSERVER, |
| false); |
| break; |
| case "S7": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA256" }, |
| new String[] { |
| "END_ENTITY_SHA1-INTER_CA_SHA256-ROOT_CA_SHA256", |
| "INTER_CA_SHA256-ROOT_CA_SHA256" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| false); |
| break; |
| |
| // Cases C8 and S8 use SHA256 root CA in trust store, |
| // and use SHA1 intermediate CA and SHA256 end entity in key store. |
| // C8 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S8 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should be blocked. |
| case "C8": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA256" }, |
| new String[] { |
| "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA256", |
| "INTER_CA_SHA1-ROOT_CA_SHA256" }, |
| NOSHA1, |
| TLSSERVER, |
| false); |
| break; |
| case "S8": |
| testConstraint( |
| new String[] { "ROOT_CA_SHA256" }, |
| new String[] { |
| "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA256", |
| "INTER_CA_SHA1-ROOT_CA_SHA256" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| false); |
| break; |
| |
| // Cases C9 and S9 use SHA256 root CA and SHA1 intermediate CA in trust store, |
| // and use SHA256 end entity in key store. |
| // C9 only sets constraint "SHA1 usage TLSServer" on client side; |
| // S9 only sets constraint "SHA1 usage TLSClient" on server side with client auth. |
| // The connection of the both cases should not be blocked. |
| case "C9": |
| testConstraint( |
| new String[] { |
| "ROOT_CA_SHA256", |
| "INTER_CA_SHA1-ROOT_CA_SHA256" }, |
| new String[] { |
| "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA256" }, |
| NOSHA1, |
| TLSSERVER, |
| true); |
| break; |
| case "S9": |
| testConstraint( |
| new String[] { |
| "ROOT_CA_SHA256", |
| "INTER_CA_SHA1-ROOT_CA_SHA256" }, |
| new String[] { |
| "END_ENTITY_SHA256-INTER_CA_SHA1-ROOT_CA_SHA256" }, |
| TLSCLIENT, |
| NOSHA1, |
| true, |
| true); |
| break; |
| } |
| |
| System.out.println("Case passed"); |
| System.out.println("========================================"); |
| } |
| |
| private static String loadCert(String certName) { |
| try { |
| Path certFilePath = Paths.get(CERT_DIR, certName + ".cer"); |
| return String.join("\n", |
| Files.lines(certFilePath).filter((String line) -> { |
| return !line.startsWith("Certificate") |
| && !line.startsWith(" "); |
| }).collect(Collectors.toList())); |
| } catch (IOException e) { |
| throw new RuntimeException("Load certificate failed", e); |
| } |
| } |
| |
| private static String loadPrivKey(String certName) { |
| Path priveKeyFilePath = Paths.get(CERT_DIR, certName + "-PRIV.key"); |
| try { |
| return new String(Files.readAllBytes(priveKeyFilePath)); |
| } catch (IOException e) { |
| throw new RuntimeException("Load private key failed", e); |
| } |
| } |
| } |