1/* 2 * Copyright (C) 2015 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 17package libcore.net; 18 19import junit.framework.TestCase; 20import libcore.io.IoUtils; 21import java.io.Closeable; 22import java.io.IOException; 23import java.net.JarURLConnection; 24import java.net.ServerSocket; 25import java.net.Socket; 26import java.net.URL; 27import java.util.Arrays; 28import java.util.HashMap; 29import java.util.Map; 30import java.util.concurrent.Callable; 31import java.util.concurrent.Future; 32import java.util.concurrent.FutureTask; 33import java.util.concurrent.TimeUnit; 34import java.util.concurrent.TimeoutException; 35import java.util.logging.ErrorManager; 36import java.util.logging.Level; 37import java.util.logging.LogRecord; 38import java.util.logging.SocketHandler; 39 40public class NetworkSecurityPolicyTest extends TestCase { 41 42 private NetworkSecurityPolicy mOriginalPolicy; 43 44 @Override 45 protected void setUp() throws Exception { 46 super.setUp(); 47 mOriginalPolicy = NetworkSecurityPolicy.getInstance(); 48 } 49 50 @Override 51 protected void tearDown() throws Exception { 52 try { 53 NetworkSecurityPolicy.setInstance(mOriginalPolicy); 54 } finally { 55 super.tearDown(); 56 } 57 } 58 59 public void testCleartextTrafficPolicySetterAndGetter() { 60 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); 61 assertEquals(false, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); 62 63 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); 64 assertEquals(true, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); 65 66 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); 67 assertEquals(false, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); 68 69 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); 70 assertEquals(true, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); 71 } 72 73 public void testHostnameAwareCleartextTrafficPolicySetterAndGetter() { 74 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); 75 assertEquals(false, 76 NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted("localhost")); 77 78 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); 79 assertEquals(true, 80 NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted("localhost")); 81 82 TestNetworkSecurityPolicy policy = new TestNetworkSecurityPolicy(false); 83 policy.addHostMapping("localhost", true); 84 policy.addHostMapping("example.com", false); 85 NetworkSecurityPolicy.setInstance(policy); 86 assertEquals(false, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); 87 assertEquals(true, 88 NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted("localhost")); 89 assertEquals(false, 90 NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted("example.com")); 91 92 } 93 94 public void testCleartextTrafficPolicyWithHttpURLConnection() throws Exception { 95 // Assert that client transmits some data when cleartext traffic is permitted. 96 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); 97 try (CapturingServerSocket server = new CapturingServerSocket()) { 98 URL url = new URL("http://localhost:" + server.getPort() + "/test.txt"); 99 try { 100 url.openConnection().getContent(); 101 fail(); 102 } catch (IOException expected) { 103 } 104 server.assertDataTransmittedByClient(); 105 } 106 107 // Assert that client does not transmit any data when cleartext traffic is not permitted and 108 // that URLConnection.openConnection or getContent fail with an IOException. 109 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); 110 try (CapturingServerSocket server = new CapturingServerSocket()) { 111 URL url = new URL("http://localhost:" + server.getPort() + "/test.txt"); 112 try { 113 url.openConnection().getContent(); 114 fail(); 115 } catch (IOException expected) { 116 } 117 server.assertNoDataTransmittedByClient(); 118 } 119 } 120 121 public void testCleartextTrafficPolicyWithFtpURLConnection() throws Exception { 122 // Assert that client transmits some data when cleartext traffic is permitted. 123 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); 124 byte[] serverReplyOnConnect = "220\r\n".getBytes("US-ASCII"); 125 try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { 126 URL url = new URL("ftp://localhost:" + server.getPort() + "/test.txt"); 127 try { 128 url.openConnection().getContent(); 129 fail(); 130 } catch (IOException expected) { 131 } 132 server.assertDataTransmittedByClient(); 133 } 134 135 // Assert that client does not transmit any data when cleartext traffic is not permitted and 136 // that URLConnection.openConnection or getContent fail with an IOException. 137 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); 138 try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { 139 URL url = new URL("ftp://localhost:" + server.getPort() + "/test.txt"); 140 try { 141 url.openConnection().getContent(); 142 fail(); 143 } catch (IOException expected) { 144 } 145 server.assertNoDataTransmittedByClient(); 146 } 147 } 148 149 public void testCleartextTrafficPolicyWithJarHttpURLConnection() throws Exception { 150 // Assert that client transmits some data when cleartext traffic is permitted. 151 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); 152 try (CapturingServerSocket server = new CapturingServerSocket()) { 153 URL url = new URL("jar:http://localhost:" + server.getPort() + "/test.jar!/"); 154 try { 155 ((JarURLConnection) url.openConnection()).getManifest(); 156 fail(); 157 } catch (IOException expected) { 158 } 159 server.assertDataTransmittedByClient(); 160 } 161 162 // Assert that client does not transmit any data when cleartext traffic is not permitted and 163 // that JarURLConnection.openConnection or getManifest fail with an IOException. 164 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); 165 try (CapturingServerSocket server = new CapturingServerSocket()) { 166 URL url = new URL("jar:http://localhost:" + server.getPort() + "/test.jar!/"); 167 try { 168 ((JarURLConnection) url.openConnection()).getManifest(); 169 fail(); 170 } catch (IOException expected) { 171 } 172 server.assertNoDataTransmittedByClient(); 173 } 174 } 175 176 public void testCleartextTrafficPolicyWithJarFtpURLConnection() throws Exception { 177 // Assert that client transmits some data when cleartext traffic is permitted. 178 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); 179 byte[] serverReplyOnConnect = "220\r\n".getBytes("US-ASCII"); 180 try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { 181 URL url = new URL("jar:ftp://localhost:" + server.getPort() + "/test.jar!/"); 182 try { 183 ((JarURLConnection) url.openConnection()).getManifest(); 184 fail(); 185 } catch (IOException expected) { 186 } 187 server.assertDataTransmittedByClient(); 188 } 189 190 // Assert that client does not transmit any data when cleartext traffic is not permitted and 191 // that JarURLConnection.openConnection or getManifest fail with an IOException. 192 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); 193 try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { 194 URL url = new URL("jar:ftp://localhost:" + server.getPort() + "/test.jar!/"); 195 try { 196 ((JarURLConnection) url.openConnection()).getManifest(); 197 fail(); 198 } catch (IOException expected) { 199 } 200 server.assertNoDataTransmittedByClient(); 201 } 202 } 203 204 public void testCleartextTrafficPolicyWithLoggingSocketHandler() throws Exception { 205 // Assert that client transmits some data when cleartext traffic is permitted. 206 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); 207 try (CapturingServerSocket server = new CapturingServerSocket()) { 208 SocketHandler logger = new SocketHandler("localhost", server.getPort()); 209 MockErrorManager mockErrorManager = new MockErrorManager(); 210 logger.setErrorManager(mockErrorManager); 211 logger.setLevel(Level.ALL); 212 LogRecord record = new LogRecord(Level.INFO, "A log record"); 213 assertTrue(logger.isLoggable(record)); 214 logger.publish(record); 215 assertNull(mockErrorManager.getMostRecentException()); 216 server.assertDataTransmittedByClient(); 217 } 218 219 // Assert that client does not transmit any data when cleartext traffic is not permitted. 220 NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); 221 try (CapturingServerSocket server = new CapturingServerSocket()) { 222 try { 223 new SocketHandler("localhost", server.getPort()); 224 fail(); 225 } catch (IOException expected) { 226 } 227 server.assertNoDataTransmittedByClient(); 228 } 229 } 230 231 /** 232 * Server socket which listens on a local port and captures the first chunk of data transmitted 233 * by the client. 234 */ 235 private static class CapturingServerSocket implements Closeable { 236 private final ServerSocket mSocket; 237 private final int mPort; 238 private final Thread mListeningThread; 239 private final FutureTask<byte[]> mFirstChunkReceivedFuture; 240 241 /** 242 * Constructs a new socket listening on a local port. 243 */ 244 public CapturingServerSocket() throws IOException { 245 this(null); 246 } 247 248 /** 249 * Constructs a new socket listening on a local port, which sends the provided reply as 250 * soon as a client connects to it. 251 */ 252 public CapturingServerSocket(final byte[] replyOnConnect) throws IOException { 253 mSocket = new ServerSocket(0); 254 mPort = mSocket.getLocalPort(); 255 mFirstChunkReceivedFuture = new FutureTask<byte[]>(new Callable<byte[]>() { 256 @Override 257 public byte[] call() throws Exception { 258 try (Socket client = mSocket.accept()) { 259 // Reply (if requested) 260 if (replyOnConnect != null) { 261 client.getOutputStream().write(replyOnConnect); 262 client.getOutputStream().flush(); 263 } 264 265 // Read request 266 byte[] buf = new byte[64 * 1024]; 267 int chunkSize = client.getInputStream().read(buf); 268 if (chunkSize == -1) { 269 // Connection closed without any data received 270 return new byte[0]; 271 } 272 // Received some data 273 return Arrays.copyOf(buf, chunkSize); 274 } finally { 275 IoUtils.closeQuietly(mSocket); 276 } 277 } 278 }); 279 mListeningThread = new Thread(mFirstChunkReceivedFuture); 280 mListeningThread.start(); 281 } 282 283 public int getPort() { 284 return mPort; 285 } 286 287 public Future<byte[]> getFirstReceivedChunkFuture() { 288 return mFirstChunkReceivedFuture; 289 } 290 291 @Override 292 public void close() { 293 IoUtils.closeQuietly(mSocket); 294 mListeningThread.interrupt(); 295 } 296 297 private void assertDataTransmittedByClient() 298 throws Exception { 299 byte[] firstChunkFromClient = getFirstReceivedChunkFuture().get(2, TimeUnit.SECONDS); 300 if ((firstChunkFromClient == null) || (firstChunkFromClient.length == 0)) { 301 fail("Client did not transmit any data to server"); 302 } 303 } 304 305 private void assertNoDataTransmittedByClient() 306 throws Exception { 307 byte[] firstChunkFromClient; 308 try { 309 firstChunkFromClient = getFirstReceivedChunkFuture().get(2, TimeUnit.SECONDS); 310 } catch (TimeoutException expected) { 311 return; 312 } 313 if ((firstChunkFromClient != null) && (firstChunkFromClient.length > 0)) { 314 fail("Client transmitted " + firstChunkFromClient.length+ " bytes: " 315 + new String(firstChunkFromClient, "US-ASCII")); 316 } 317 } 318 } 319 320 private static class MockErrorManager extends ErrorManager { 321 private Exception mMostRecentException; 322 323 public Exception getMostRecentException() { 324 synchronized (this) { 325 return mMostRecentException; 326 } 327 } 328 329 @Override 330 public void error(String message, Exception exception, int errorCode) { 331 synchronized (this) { 332 mMostRecentException = exception; 333 } 334 } 335 } 336 337 private static class TestNetworkSecurityPolicy extends NetworkSecurityPolicy { 338 private final boolean mCleartextTrafficPermitted; 339 private final Map<String, Boolean> mHostMap = new HashMap<String, Boolean>(); 340 341 public TestNetworkSecurityPolicy(boolean cleartextTrafficPermitted) { 342 mCleartextTrafficPermitted = cleartextTrafficPermitted; 343 } 344 345 public void addHostMapping(String hostname, boolean isCleartextTrafficPermitted) { 346 mHostMap.put(hostname, isCleartextTrafficPermitted); 347 } 348 349 @Override 350 public boolean isCleartextTrafficPermitted() { 351 return mCleartextTrafficPermitted; 352 } 353 354 @Override 355 public boolean isCleartextTrafficPermitted(String hostname) { 356 if (mHostMap.containsKey(hostname)) { 357 return mHostMap.get(hostname); 358 } 359 360 return isCleartextTrafficPermitted(); 361 } 362 } 363} 364