1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.cts; 18 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.assertTrue; 21 import static org.junit.Assert.fail; 22 23 import android.net.SSLCertificateSocketFactory; 24 import android.platform.test.annotations.AppModeFull; 25 import java.io.IOException; 26 import java.net.InetAddress; 27 import java.net.InetSocketAddress; 28 import java.net.Socket; 29 import java.net.SocketAddress; 30 import java.net.UnknownHostException; 31 import java.util.Arrays; 32 import java.util.List; 33 import java.util.stream.Collectors; 34 import javax.net.ssl.HostnameVerifier; 35 import javax.net.ssl.HttpsURLConnection; 36 import javax.net.ssl.SSLPeerUnverifiedException; 37 import javax.net.ssl.SSLSession; 38 import libcore.javax.net.ssl.SSLConfigurationAsserts; 39 import org.junit.After; 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 import org.junit.runners.JUnit4; 44 45 @RunWith(JUnit4.class) 46 public class SSLCertificateSocketFactoryTest { 47 // TEST_HOST should point to a web server with a valid TLS certificate. 48 private static final String TEST_HOST = "www.google.com"; 49 private static final int HTTPS_PORT = 443; 50 private HostnameVerifier mDefaultVerifier; 51 private SSLCertificateSocketFactory mSocketFactory; 52 private InetAddress mLocalAddress; 53 // InetAddress obtained by resolving TEST_HOST. 54 private InetAddress mTestHostAddress; 55 // SocketAddress combining mTestHostAddress and HTTPS_PORT. 56 private List<SocketAddress> mTestSocketAddresses; 57 58 @Before setUp()59 public void setUp() { 60 // Expected state before each test method is that 61 // HttpsURLConnection.getDefaultHostnameVerifier() will return the system default. 62 mDefaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); 63 mSocketFactory = (SSLCertificateSocketFactory) 64 SSLCertificateSocketFactory.getDefault(1000 /* handshakeTimeoutMillis */); 65 assertNotNull(mSocketFactory); 66 InetAddress[] addresses; 67 try { 68 addresses = InetAddress.getAllByName(TEST_HOST); 69 mTestHostAddress = addresses[0]; 70 } catch (UnknownHostException uhe) { 71 throw new AssertionError( 72 "Unable to test SSLCertificateSocketFactory: cannot resolve " + TEST_HOST, uhe); 73 } 74 75 mTestSocketAddresses = Arrays.stream(addresses) 76 .map(addr -> new InetSocketAddress(addr, HTTPS_PORT)) 77 .collect(Collectors.toList()); 78 79 // Find the local IP address which will be used to connect to TEST_HOST. 80 try { 81 Socket testSocket = new Socket(TEST_HOST, HTTPS_PORT); 82 mLocalAddress = testSocket.getLocalAddress(); 83 testSocket.close(); 84 } catch (IOException ioe) { 85 throw new AssertionError("" 86 + "Unable to test SSLCertificateSocketFactory: cannot connect to " 87 + TEST_HOST, ioe); 88 } 89 } 90 91 // Restore the system default hostname verifier after each test. 92 @After restoreDefaultHostnameVerifier()93 public void restoreDefaultHostnameVerifier() { 94 HttpsURLConnection.setDefaultHostnameVerifier(mDefaultVerifier); 95 } 96 97 @Test testDefaultConfiguration()98 public void testDefaultConfiguration() throws Exception { 99 SSLConfigurationAsserts.assertSSLSocketFactoryDefaultConfiguration(mSocketFactory); 100 } 101 102 @Test testAccessProperties()103 public void testAccessProperties() { 104 mSocketFactory.getSupportedCipherSuites(); 105 mSocketFactory.getDefaultCipherSuites(); 106 } 107 108 /** 109 * Tests the {@code createSocket()} cases which are expected to fail with {@code IOException}. 110 */ 111 @Test 112 @AppModeFull(reason = "Socket cannot bind in instant app mode") createSocket_io_error_expected()113 public void createSocket_io_error_expected() { 114 // Connect to the localhost HTTPS port. Should result in connection refused IOException 115 // because no service should be listening on that port. 116 InetAddress localhostAddress = InetAddress.getLoopbackAddress(); 117 try { 118 mSocketFactory.createSocket(localhostAddress, HTTPS_PORT); 119 fail(); 120 } catch (IOException e) { 121 // expected 122 } 123 124 // Same, but also binding to a local address. 125 try { 126 mSocketFactory.createSocket(localhostAddress, HTTPS_PORT, localhostAddress, 0); 127 fail(); 128 } catch (IOException e) { 129 // expected 130 } 131 132 // Same, wrapping an existing plain socket which is in an unconnected state. 133 try { 134 Socket socket = new Socket(); 135 mSocketFactory.createSocket(socket, "localhost", HTTPS_PORT, true); 136 fail(); 137 } catch (IOException e) { 138 // expected 139 } 140 } 141 142 /** 143 * Tests hostname verification for 144 * {@link SSLCertificateSocketFactory#createSocket(String, int)}. 145 * 146 * <p>This method should return a socket which is fully connected (i.e. TLS handshake complete) 147 * and whose peer TLS certificate has been verified to have the correct hostname. 148 * 149 * <p>{@link SSLCertificateSocketFactory} is documented to verify hostnames using 150 * the {@link HostnameVerifier} returned by 151 * {@link HttpsURLConnection#getDefaultHostnameVerifier}, so this test connects twice, 152 * once with the system default {@link HostnameVerifier} which is expected to succeed, 153 * and once after installing a {@link NegativeHostnameVerifier} which will cause 154 * {@link SSLCertificateSocketFactory#verifyHostname} to throw a 155 * {@link SSLPeerUnverifiedException}. 156 * 157 * <p>These tests only test the hostname verification logic in SSLCertificateSocketFactory, 158 * other TLS failure modes and the default HostnameVerifier are tested elsewhere, see 159 * {@link com.squareup.okhttp.internal.tls.HostnameVerifierTest} and 160 * https://android.googlesource.com/platform/external/boringssl/+/refs/heads/master/src/ssl/test 161 * 162 * <p>Tests the following behaviour:- 163 * <ul> 164 * <li>TEST_SERVER is available and has a valid TLS certificate 165 * <li>{@code createSocket()} verifies the remote hostname is correct using 166 * {@link HttpsURLConnection#getDefaultHostnameVerifier} 167 * <li>{@link SSLPeerUnverifiedException} is thrown when the remote hostname is invalid 168 * </ul> 169 * 170 * <p>See also http://b/2807618. 171 */ 172 @Test createSocket_simple_with_hostname_verification()173 public void createSocket_simple_with_hostname_verification() throws Exception { 174 Socket socket = mSocketFactory.createSocket(TEST_HOST, HTTPS_PORT); 175 assertConnectedSocket(socket); 176 socket.close(); 177 178 HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier()); 179 try { 180 mSocketFactory.createSocket(TEST_HOST, HTTPS_PORT); 181 fail(); 182 } catch (SSLPeerUnverifiedException expected) { 183 // expected 184 } 185 } 186 187 /** 188 * Tests hostname verification for 189 * {@link SSLCertificateSocketFactory#createSocket(Socket, String, int, boolean)}. 190 * 191 * <p>This method should return a socket which is fully connected (i.e. TLS handshake complete) 192 * and whose peer TLS certificate has been verified to have the correct hostname. 193 * 194 * <p>The TLS socket returned is wrapped around the plain socket passed into 195 * {@code createSocket()}. 196 * 197 * <p>See {@link #createSocket_simple_with_hostname_verification()} for test methodology. 198 */ 199 @Test createSocket_wrapped_with_hostname_verification()200 public void createSocket_wrapped_with_hostname_verification() throws Exception { 201 Socket underlying = new Socket(TEST_HOST, HTTPS_PORT); 202 Socket socket = mSocketFactory.createSocket(underlying, TEST_HOST, HTTPS_PORT, true); 203 assertConnectedSocket(socket); 204 socket.close(); 205 206 HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier()); 207 try { 208 underlying = new Socket(TEST_HOST, HTTPS_PORT); 209 mSocketFactory.createSocket(underlying, TEST_HOST, HTTPS_PORT, true); 210 fail(); 211 } catch (SSLPeerUnverifiedException expected) { 212 // expected 213 } 214 } 215 216 /** 217 * Tests hostname verification for 218 * {@link SSLCertificateSocketFactory#createSocket(String, int, InetAddress, int)}. 219 * 220 * <p>This method should return a socket which is fully connected (i.e. TLS handshake complete) 221 * and whose peer TLS certificate has been verified to have the correct hostname. 222 * 223 * <p>The TLS socket returned is also bound to the local address determined in {@link #setUp} to 224 * be used for connections to TEST_HOST, and a wildcard port. 225 * 226 * <p>See {@link #createSocket_simple_with_hostname_verification()} for test methodology. 227 */ 228 @Test 229 @AppModeFull(reason = "Socket cannot bind in instant app mode") createSocket_bound_with_hostname_verification()230 public void createSocket_bound_with_hostname_verification() throws Exception { 231 Socket socket = mSocketFactory.createSocket(TEST_HOST, HTTPS_PORT, mLocalAddress, 0); 232 assertConnectedSocket(socket); 233 socket.close(); 234 235 HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier()); 236 try { 237 mSocketFactory.createSocket(TEST_HOST, HTTPS_PORT, mLocalAddress, 0); 238 fail(); 239 } catch (SSLPeerUnverifiedException expected) { 240 // expected 241 } 242 } 243 244 /** 245 * Tests hostname verification for 246 * {@link SSLCertificateSocketFactory#createSocket(InetAddress, int)}. 247 * 248 * <p>This method should return a socket which the documentation describes as "unconnected", 249 * which actually means that the socket is fully connected at the TCP layer but TLS handshaking 250 * and hostname verification have not yet taken place. 251 * 252 * <p>Behaviour is tested by installing a {@link NegativeHostnameVerifier} and by calling 253 * {@link #assertConnectedSocket} to ensure TLS handshaking but no hostname verification takes 254 * place. Next, {@link SSLCertificateSocketFactory#verifyHostname} is called to ensure 255 * that hostname verification is using the {@link HostnameVerifier} returned by 256 * {@link HttpsURLConnection#getDefaultHostnameVerifier} as documented. 257 * 258 * <p>Tests the following behaviour:- 259 * <ul> 260 * <li>TEST_SERVER is available and has a valid TLS certificate 261 * <li>{@code createSocket()} does not verify the remote hostname 262 * <li>Calling {@link SSLCertificateSocketFactory#verifyHostname} on the returned socket 263 * throws {@link SSLPeerUnverifiedException} if the remote hostname is invalid 264 * </ul> 265 */ 266 @Test createSocket_simple_no_hostname_verification()267 public void createSocket_simple_no_hostname_verification() throws Exception{ 268 HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier()); 269 Socket socket = mSocketFactory.createSocket(mTestHostAddress, HTTPS_PORT); 270 // Need to provide the expected hostname here or the TLS handshake will 271 // be unable to supply SNI to the remote host. 272 mSocketFactory.setHostname(socket, TEST_HOST); 273 assertConnectedSocket(socket); 274 try { 275 SSLCertificateSocketFactory.verifyHostname(socket, TEST_HOST); 276 fail(); 277 } catch (SSLPeerUnverifiedException expected) { 278 // expected 279 } 280 HttpsURLConnection.setDefaultHostnameVerifier(mDefaultVerifier); 281 SSLCertificateSocketFactory.verifyHostname(socket, TEST_HOST); 282 socket.close(); 283 } 284 285 /** 286 * Tests hostname verification for 287 * {@link SSLCertificateSocketFactory#createSocket(InetAddress, int, InetAddress, int)}. 288 * 289 * <p>This method should return a socket which the documentation describes as "unconnected", 290 * which actually means that the socket is fully connected at the TCP layer but TLS handshaking 291 * and hostname verification have not yet taken place. 292 * 293 * <p>The TLS socket returned is also bound to the local address determined in {@link #setUp} to 294 * be used for connections to TEST_HOST, and a wildcard port. 295 * 296 * <p>See {@link #createSocket_simple_no_hostname_verification()} for test methodology. 297 */ 298 @Test 299 @AppModeFull(reason = "Socket cannot bind in instant app mode") createSocket_bound_no_hostname_verification()300 public void createSocket_bound_no_hostname_verification() throws Exception{ 301 HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier()); 302 Socket socket = 303 mSocketFactory.createSocket(mTestHostAddress, HTTPS_PORT, mLocalAddress, 0); 304 // Need to provide the expected hostname here or the TLS handshake will 305 // be unable to supply SNI to the peer. 306 mSocketFactory.setHostname(socket, TEST_HOST); 307 assertConnectedSocket(socket); 308 try { 309 SSLCertificateSocketFactory.verifyHostname(socket, TEST_HOST); 310 fail(); 311 } catch (SSLPeerUnverifiedException expected) { 312 // expected 313 } 314 HttpsURLConnection.setDefaultHostnameVerifier(mDefaultVerifier); 315 SSLCertificateSocketFactory.verifyHostname(socket, TEST_HOST); 316 socket.close(); 317 } 318 319 /** 320 * Asserts a socket is fully connected to the expected peer. 321 * 322 * <p>For the variants of createSocket which verify the remote hostname, 323 * {@code socket} should already be fully connected. 324 * 325 * <p>For the non-verifying variants, retrieving the input stream will trigger a TLS handshake 326 * and so may throw an exception, for example if the peer's certificate is invalid. 327 * 328 * <p>Does no hostname verification. 329 */ assertConnectedSocket(Socket socket)330 private void assertConnectedSocket(Socket socket) throws Exception { 331 assertNotNull(socket); 332 assertTrue(socket.isConnected()); 333 assertNotNull(socket.getInputStream()); 334 assertNotNull(socket.getOutputStream()); 335 assertTrue(mTestSocketAddresses.contains(socket.getRemoteSocketAddress())); 336 } 337 338 /** 339 * A HostnameVerifier which always returns false to simulate a server returning a 340 * certificate which does not match the expected hostname. 341 */ 342 private static class NegativeHostnameVerifier implements HostnameVerifier { 343 @Override verify(String hostname, SSLSession sslSession)344 public boolean verify(String hostname, SSLSession sslSession) { 345 return false; 346 } 347 } 348 } 349