1 /*
   2  * Copyright (c) 2017, 2019, 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.IOException;
  25 import java.io.InputStream;
  26 import java.io.OutputStream;
  27 import java.net.InetSocketAddress;
  28 import java.security.cert.X509Certificate;
  29 import java.util.ArrayList;
  30 import java.util.List;
  31 
  32 import javax.net.ssl.SSLContext;
  33 import javax.net.ssl.SSLParameters;
  34 import javax.net.ssl.SSLSession;
  35 import javax.net.ssl.SSLSocket;
  36 import javax.net.ssl.SSLSocketFactory;
  37 
  38 /*
  39  * A simple SSL socket client.
  40  */
  41 public class Client {
  42 
  43     private final SSLSocket socket;
  44 
  45     public Client(SSLContext context) throws Exception {
  46         SSLSocketFactory socketFactory = context.getSocketFactory();
  47         socket = (SSLSocket) socketFactory.createSocket();
  48         socket.setSoTimeout(Utils.TIMEOUT);
  49     }
  50 
  51     public Client(Cert... certs) throws Exception {
  52         this(Utils.createSSLContext(certs));
  53     }
  54 
  55     private SSLSession getSession() {
  56         return socket.getSession();
  57     }
  58 
  59     private void setEnabledCipherSuites(String... cipherSuites) {
  60         socket.setEnabledCipherSuites(cipherSuites);
  61     }
  62 
  63     private void setEnabledProtocols(String... protocols) {
  64         socket.setEnabledProtocols(protocols);
  65     }
  66 
  67     @SuppressWarnings(value = { "unchecked", "rawtypes" })
  68     private void setServerName(String hostname) {
  69         List serverNames = new ArrayList();
  70         serverNames.add(createSNIHostName(hostname));
  71         SSLParameters params = socket.getSSLParameters();
  72         params.setServerNames(serverNames);
  73         socket.setSSLParameters(params);
  74     }
  75 
  76     // Create SNIHostName via reflection due to pre-8 JDK builds don't support
  77     // SNI. Those JDK builds cannot find classes SNIServerName and SNIHostName.
  78     private Object createSNIHostName(String hostname) {
  79         try {
  80             Class<?> clazz = Class.forName("javax.net.ssl.SNIHostName");
  81             return clazz.getConstructor(String.class).newInstance(hostname);
  82         } catch (Exception e) {
  83             throw new RuntimeException("Creates SNIHostName failed!", e);
  84         }
  85     }
  86 
  87     private void setApplicationProtocols(String... protocols) {
  88         SSLParameters params = socket.getSSLParameters();
  89         params.setApplicationProtocols(protocols);
  90         socket.setSSLParameters(params);
  91     }
  92 
  93     private String getNegotiatedApplicationProtocol() {
  94         return socket.getApplicationProtocol();
  95     }
  96 
  97     private void oneTimeConnect(String host, int port) throws IOException {
  98         socket.connect(new InetSocketAddress(host, port));
  99 
 100         OutputStream out = socket.getOutputStream();
 101         out.write('C');
 102         out.flush();
 103 
 104         InputStream in = socket.getInputStream();
 105         in.read();
 106     }
 107 
 108     public void close() throws IOException {
 109         socket.close();
 110     }
 111 
 112     public static void main(String[] args) throws IOException {
 113         System.out.println("----- Client start -----");
 114         int port = Integer.valueOf(System.getProperty(Utils.PROP_PORT));
 115 
 116         String protocol = System.getProperty(Utils.PROP_PROTOCOL);
 117         String cipherSuite = System.getProperty(Utils.PROP_CIPHER_SUITE);
 118         String serverName = System.getProperty(Utils.PROP_SERVER_NAME);
 119         String appProtocols = System.getProperty(Utils.PROP_APP_PROTOCOLS);
 120         boolean supportsSNIOnServer
 121                 = Utils.getBoolProperty(Utils.PROP_SUPPORTS_SNI_ON_SERVER);
 122         boolean supportsSNIOnClient
 123                 = Utils.getBoolProperty(Utils.PROP_SUPPORTS_SNI_ON_CLIENT);
 124         boolean supportsALPNOnServer
 125                 = Utils.getBoolProperty(Utils.PROP_SUPPORTS_ALPN_ON_SERVER);
 126         boolean supportsALPNOnClient
 127                 = Utils.getBoolProperty(Utils.PROP_SUPPORTS_ALPN_ON_CLIENT);
 128         boolean negativeCase
 129                 = Utils.getBoolProperty(Utils.PROP_NEGATIVE_CASE_ON_CLIENT);
 130         System.out.println(Utils.join(Utils.PARAM_DELIMITER,
 131                 "ClientJDK=" + System.getProperty(Utils.PROP_CLIENT_JDK),
 132                 "Protocol=" + protocol,
 133                 "CipherSuite=" + cipherSuite,
 134                 "ServerName=" + serverName,
 135                 "AppProtocols=" + appProtocols));
 136 
 137         Status status = Status.SUCCESS;
 138         Client client = null;
 139         try {
 140             client = new Client(Cert.getCerts(CipherSuite.cipherSuite(cipherSuite)));
 141             client.setEnabledProtocols(protocol);
 142             client.setEnabledCipherSuites(cipherSuite);
 143 
 144             if (serverName != null) {
 145                 if (supportsSNIOnClient) {
 146                     client.setServerName(serverName);
 147                 } else {
 148                     System.out.println(
 149                             "Ignored due to client doesn't support SNI.");
 150                 }
 151             }
 152 
 153             if (appProtocols != null) {
 154                 if (supportsALPNOnClient) {
 155                     client.setApplicationProtocols(
 156                             Utils.split(appProtocols, Utils.VALUE_DELIMITER));
 157                 } else {
 158                     System.out.println(
 159                             "Ignored due to client doesn't support ALPN.");
 160                 }
 161             }
 162 
 163             client.oneTimeConnect("localhost", port);
 164 
 165             if (serverName != null && supportsSNIOnServer
 166                     && supportsSNIOnClient) {
 167                 X509Certificate cert
 168                         = (X509Certificate) client.getSession().getPeerCertificates()[0];
 169                 String subject
 170                         = cert.getSubjectX500Principal().getName();
 171                 if (!subject.contains(serverName)) {
 172                     System.out.println("Unexpected server: " + subject);
 173                     status = Status.FAIL;
 174                 }
 175             }
 176 
 177             if (appProtocols != null && supportsALPNOnServer
 178                     && supportsALPNOnClient) {
 179                 String negoAppProtocol
 180                         = client.getNegotiatedApplicationProtocol();
 181                 String expectedNegoAppProtocol
 182                         = System.getProperty(Utils.PROP_NEGO_APP_PROTOCOL);
 183                 if (!expectedNegoAppProtocol.equals(negoAppProtocol)) {
 184                     System.out.println("Unexpected negotiated app protocol: "
 185                             + negoAppProtocol);
 186                     status = Status.FAIL;
 187                 }
 188             }
 189 
 190             if (status != Status.FAIL) {
 191                 status = negativeCase
 192                        ? Status.UNEXPECTED_SUCCESS
 193                        : Status.SUCCESS;
 194             }
 195         } catch (Exception exception) {
 196             status = Utils.handleException(exception, negativeCase);
 197         } finally {
 198             if (client != null) {
 199                 client.close();
 200             }
 201         }
 202 
 203         System.out.println("STATUS: " + status);
 204         System.out.println("----- Client end -----");
 205     }
 206 }