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