1 /* 2 * Copyright (c) 2015, 2016, 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 /* 25 * @test 26 * @bug 8087112 27 * @modules jdk.incubator.httpclient 28 * java.logging 29 * jdk.httpserver 30 * @library /lib/testlibrary/ 31 * @build jdk.testlibrary.SimpleSSLContext 32 * @compile ../../../../com/sun/net/httpserver/LogFilter.java 33 * @compile ../../../../com/sun/net/httpserver/FileServerHandler.java 34 * @compile ../ProxyServer.java 35 * 36 * @run main/othervm/secure=java.lang.SecurityManager/policy=0.policy Security 0 37 * @run main/othervm/secure=java.lang.SecurityManager/policy=2.policy Security 2 38 * @run main/othervm/secure=java.lang.SecurityManager/policy=3.policy Security 3 39 * @run main/othervm/secure=java.lang.SecurityManager/policy=4.policy Security 4 40 * @run main/othervm/secure=java.lang.SecurityManager/policy=5.policy Security 5 41 * @run main/othervm/secure=java.lang.SecurityManager/policy=6.policy Security 6 42 * @run main/othervm/secure=java.lang.SecurityManager/policy=7.policy Security 7 43 * @run main/othervm/secure=java.lang.SecurityManager/policy=8.policy Security 8 44 * @run main/othervm/secure=java.lang.SecurityManager/policy=9.policy Security 9 45 * @run main/othervm/secure=java.lang.SecurityManager/policy=0.policy Security 13 46 * @run main/othervm/secure=java.lang.SecurityManager/policy=14.policy Security 14 47 * @run main/othervm/secure=java.lang.SecurityManager/policy=15.policy -Djava.security.debug=access:domain,failure Security 15 48 */ 49 50 // Tests 1, 10, 11 and 12 executed from Driver 51 52 import com.sun.net.httpserver.Headers; 53 import com.sun.net.httpserver.HttpContext; 54 import com.sun.net.httpserver.HttpExchange; 55 import com.sun.net.httpserver.HttpHandler; 56 import com.sun.net.httpserver.HttpServer; 57 import com.sun.net.httpserver.HttpsServer; 58 import java.io.IOException; 59 import java.io.InputStream; 60 import java.io.File; 61 import java.io.OutputStream; 62 import java.lang.reflect.Constructor; 63 import java.net.BindException; 64 import java.net.InetSocketAddress; 65 import java.net.ProxySelector; 66 import java.net.URI; 67 import java.net.URLClassLoader; 68 import java.net.URL; 69 import jdk.incubator.http.HttpHeaders; 70 import jdk.incubator.http.HttpClient; 71 import jdk.incubator.http.HttpRequest; 72 import jdk.incubator.http.HttpResponse; 73 import java.nio.charset.StandardCharsets; 74 import java.nio.file.Files; 75 import java.nio.file.Path; 76 import java.nio.ByteBuffer; 77 import java.nio.file.Paths; 78 import java.nio.file.StandardCopyOption; 79 import java.util.LinkedList; 80 import java.util.List; 81 import java.util.concurrent.CompletableFuture; 82 import java.util.concurrent.CompletionException; 83 import java.util.concurrent.CompletionStage; 84 import java.util.concurrent.ExecutionException; 85 import java.util.concurrent.Executor; 86 import java.util.concurrent.Executors; 87 import java.util.concurrent.ExecutorService; 88 import java.util.concurrent.Flow; 89 import java.util.logging.ConsoleHandler; 90 import java.util.logging.Level; 91 import java.util.logging.Logger; 92 import java.lang.reflect.InvocationTargetException; 93 import static jdk.incubator.http.HttpResponse.BodyHandler.asString; 94 95 /** 96 * Security checks test 97 */ 98 public class Security { 99 100 static HttpServer s1 = null; 101 static ExecutorService executor=null; 102 static int port, proxyPort; 103 static HttpClient client; 104 static String httproot, fileuri, fileroot, redirectroot; 105 static List<HttpClient> clients = new LinkedList<>(); 106 static URI uri; 107 108 interface Test { 109 void execute() throws IOException, InterruptedException; 110 } 111 112 static class TestAndResult { 113 Test test; 114 boolean result; 115 116 TestAndResult (Test t, boolean result) { 117 this.test = t; 118 this.result = result; 119 } 120 } 121 122 static TestAndResult test(boolean result, Test t) { 123 return new TestAndResult(t, result); 124 } 125 126 static TestAndResult[] tests; 127 static String testclasses; 128 static File subdir; 129 130 /** 131 * The ProxyServer class is compiled by jtreg, but we want to 132 * move it so it is not on the application claspath. We want to 133 * load it through a separate classloader so that it has a separate 134 * protection domain and security permissions. 135 * 136 * Its permissions are in the second grant block in each policy file 137 */ 138 static void setupProxy() throws IOException, ClassNotFoundException, NoSuchMethodException { 139 testclasses = System.getProperty("test.classes"); 140 subdir = new File (testclasses, "proxydir"); 141 subdir.mkdir(); 142 143 movefile("ProxyServer.class"); 144 movefile("ProxyServer$Connection.class"); 145 movefile("ProxyServer$1.class"); 146 147 URL url = subdir.toURL(); 148 System.out.println("URL for class loader = " + url); 149 URLClassLoader urlc = new URLClassLoader(new URL[] {url}); 150 proxyClass = Class.forName("ProxyServer", true, urlc); 151 proxyConstructor = proxyClass.getConstructor(Integer.class, Boolean.class); 152 } 153 154 static void movefile(String f) throws IOException { 155 Path src = Paths.get(testclasses, f); 156 Path dest = subdir.toPath().resolve(f); 157 if (!dest.toFile().exists()) { 158 System.out.printf("moving %s to %s\n", src.toString(), dest.toString()); 159 Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING); 160 } else if (src.toFile().exists()) { 161 System.out.printf("%s exists, deleting %s\n", dest.toString(), src.toString()); 162 Files.delete(src); 163 } else { 164 System.out.printf("NOT moving %s to %s\n", src.toString(), dest.toString()); 165 } 166 } 167 168 static Object getProxy(int port, boolean b) throws Throwable { 169 try { 170 return proxyConstructor.newInstance(port, b); 171 } catch (InvocationTargetException e) { 172 throw e.getTargetException(); 173 } 174 } 175 176 static Class<?> proxyClass; 177 static Constructor<?> proxyConstructor; 178 179 static void setupTests() { 180 tests = new TestAndResult[]{ 181 // (0) policy does not have permission for file. Should fail 182 test(false, () -> { // Policy 0 183 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 184 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 185 HttpResponse<?> response = client.send(request, asString()); 186 }), 187 // (1) policy has permission for file URL 188 test(true, () -> { //Policy 1 189 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 190 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 191 HttpResponse<?> response = client.send(request, asString()); 192 }), 193 // (2) policy has permission for all file URLs under /files 194 test(true, () -> { // Policy 2 195 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 196 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 197 HttpResponse<?> response = client.send(request, asString()); 198 }), 199 // (3) policy has permission for first URL but not redirected URL 200 test(false, () -> { // Policy 3 201 URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt"); 202 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 203 HttpResponse<?> response = client.send(request, asString()); 204 }), 205 // (4) policy has permission for both first URL and redirected URL 206 test(true, () -> { // Policy 4 207 URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt"); 208 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 209 HttpResponse<?> response = client.send(request, asString()); 210 }), 211 // (5) policy has permission for redirected but not first URL 212 test(false, () -> { // Policy 5 213 URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt"); 214 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 215 HttpResponse<?> response = client.send(request, asString()); 216 }), 217 // (6) policy has permission for file URL, but not method 218 test(false, () -> { //Policy 6 219 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 220 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 221 HttpResponse<?> response = client.send(request, asString()); 222 }), 223 // (7) policy has permission for file URL, method, but not header 224 test(false, () -> { //Policy 7 225 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 226 HttpRequest request = HttpRequest.newBuilder(u) 227 .header("X-Foo", "bar") 228 .GET() 229 .build(); 230 HttpResponse<?> response = client.send(request, asString()); 231 }), 232 // (8) policy has permission for file URL, method and header 233 test(true, () -> { //Policy 8 234 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 235 HttpRequest request = HttpRequest.newBuilder(u) 236 .header("X-Foo", "bar") 237 .GET() 238 .build(); 239 HttpResponse<?> response = client.send(request, asString()); 240 }), 241 // (9) policy has permission for file URL, method and header 242 test(true, () -> { //Policy 9 243 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 244 HttpRequest request = HttpRequest.newBuilder(u) 245 .headers("X-Foo", "bar", "X-Bar", "foo") 246 .GET() 247 .build(); 248 HttpResponse<?> response = client.send(request, asString()); 249 }), 250 // (10) policy has permission for destination URL but not for proxy 251 test(false, () -> { //Policy 10 252 directProxyTest(proxyPort, true); 253 }), 254 // (11) policy has permission for both destination URL and proxy 255 test(true, () -> { //Policy 11 256 directProxyTest(proxyPort, true); 257 }), 258 // (12) policy has permission for both destination URL and proxy 259 test(false, () -> { //Policy 11 260 directProxyTest(proxyPort, false); 261 }), 262 // (13) async version of test 0 263 test(false, () -> { // Policy 0 264 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 265 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 266 try { 267 HttpResponse<?> response = client.sendAsync(request, asString()).get(); 268 } catch (ExecutionException e) { 269 if (e.getCause() instanceof SecurityException) { 270 throw (SecurityException)e.getCause(); 271 } else { 272 throw new RuntimeException(e); 273 } 274 } 275 }), 276 // (14) async version of test 1 277 test(true, () -> { //Policy 1 278 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 279 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 280 try { 281 HttpResponse<?> response = client.sendAsync(request, asString()).get(); 282 } catch (ExecutionException e) { 283 if (e.getCause() instanceof SecurityException) { 284 throw (SecurityException)e.getCause(); 285 } else { 286 throw new RuntimeException(e); 287 } 288 } 289 }), 290 // (15) check that user provided unprivileged code running on a worker 291 // thread does not gain ungranted privileges. 292 test(false, () -> { //Policy 12 293 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 294 HttpRequest request = HttpRequest.newBuilder(u).GET().build(); 295 HttpResponse.BodyHandler<String> sth = asString(); 296 297 CompletableFuture<HttpResponse<String>> cf = 298 client.sendAsync(request, new HttpResponse.BodyHandler<String>() { 299 @Override 300 public HttpResponse.BodyProcessor<String> apply(int status, HttpHeaders responseHeaders) { 301 final HttpResponse.BodyProcessor<String> stproc = sth.apply(status, responseHeaders); 302 return new HttpResponse.BodyProcessor<String>() { 303 @Override 304 public CompletionStage<String> getBody() { 305 return stproc.getBody(); 306 } 307 @Override 308 public void onNext(ByteBuffer item) { 309 SecurityManager sm = System.getSecurityManager(); 310 // should succeed. 311 sm.checkPermission(new RuntimePermission("foobar")); 312 // do some mischief here 313 System.setSecurityManager(null); 314 System.setSecurityManager(sm); 315 // problem if we get this far 316 stproc.onNext(item); 317 } 318 @Override 319 public void onSubscribe(Flow.Subscription subscription) { 320 stproc.onSubscribe(subscription); 321 } 322 @Override 323 public void onError(Throwable throwable) { 324 stproc.onError(throwable); 325 } 326 @Override 327 public void onComplete() { 328 stproc.onComplete(); 329 } 330 }; 331 } 332 } 333 ); 334 try { 335 cf.join(); 336 } catch (CompletionException e) { 337 Throwable t = e.getCause(); 338 if (t instanceof SecurityException) 339 throw (SecurityException)t; 340 else 341 throw new RuntimeException(t); 342 } 343 }) 344 }; 345 } 346 347 private static void directProxyTest(int proxyPort, boolean samePort) 348 throws IOException, InterruptedException 349 { 350 Object proxy = null; 351 try { 352 proxy = getProxy(proxyPort, true); 353 } catch (BindException e) { 354 System.out.println("Bind failed"); 355 throw e; 356 } catch (Throwable ee) { 357 throw new RuntimeException(ee); 358 } 359 System.out.println("Proxy port = " + proxyPort); 360 if (!samePort) 361 proxyPort++; 362 363 InetSocketAddress addr = new InetSocketAddress("127.0.0.1", proxyPort); 364 HttpClient cl = HttpClient.newBuilder() 365 .proxy(ProxySelector.of(addr)) 366 .build(); 367 clients.add(cl); 368 369 URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); 370 HttpRequest request = HttpRequest.newBuilder(u) 371 .headers("X-Foo", "bar", "X-Bar", "foo") 372 .build(); 373 HttpResponse<?> response = cl.send(request, asString()); 374 } 375 376 static void runtest(Test r, String policy, boolean succeeds) { 377 System.out.println("Using policy file: " + policy); 378 try { 379 r.execute(); 380 if (!succeeds) { 381 System.out.println("FAILED: expected security exception"); 382 throw new RuntimeException("Failed"); 383 } 384 System.out.println (policy + " succeeded as expected"); 385 } catch (BindException e) { 386 System.exit(10); 387 } catch (SecurityException e) { 388 if (succeeds) { 389 System.out.println("FAILED"); 390 throw new RuntimeException(e); 391 } 392 System.out.println (policy + " threw exception as expected"); 393 } catch (IOException | InterruptedException ee) { 394 throw new RuntimeException(ee); 395 } 396 } 397 398 public static void main(String[] args) throws Exception { 399 try { 400 initServer(); 401 setupProxy(); 402 } catch (BindException e) { 403 System.exit(10); 404 } 405 fileroot = System.getProperty ("test.src")+ "/docs"; 406 int testnum = Integer.parseInt(args[0]); 407 String policy = args[0]; 408 409 client = HttpClient.newBuilder() 410 .followRedirects(HttpClient.Redirect.ALWAYS) 411 .build(); 412 413 clients.add(client); 414 415 try { 416 setupTests(); 417 TestAndResult tr = tests[testnum]; 418 runtest(tr.test, policy, tr.result); 419 } finally { 420 s1.stop(0); 421 executor.shutdownNow(); 422 for (HttpClient client : clients) { 423 Executor e = client.executor(); 424 if (e instanceof ExecutorService) { 425 ((ExecutorService)e).shutdownNow(); 426 } 427 } 428 } 429 } 430 431 public static void initServer() throws Exception { 432 String portstring = System.getProperty("port.number"); 433 port = portstring != null ? Integer.parseInt(portstring) : 0; 434 portstring = System.getProperty("port.number1"); 435 proxyPort = portstring != null ? Integer.parseInt(portstring) : 0; 436 437 Logger logger = Logger.getLogger("com.sun.net.httpserver"); 438 ConsoleHandler ch = new ConsoleHandler(); 439 logger.setLevel(Level.ALL); 440 ch.setLevel(Level.ALL); 441 logger.addHandler(ch); 442 String root = System.getProperty ("test.src")+ "/docs"; 443 InetSocketAddress addr = new InetSocketAddress (port); 444 s1 = HttpServer.create (addr, 0); 445 if (s1 instanceof HttpsServer) { 446 throw new RuntimeException ("should not be httpsserver"); 447 } 448 HttpHandler h = new FileServerHandler (root); 449 HttpContext c = s1.createContext ("/files", h); 450 451 HttpHandler h1 = new RedirectHandler ("/redirect"); 452 HttpContext c1 = s1.createContext ("/redirect", h1); 453 454 executor = Executors.newCachedThreadPool(); 455 s1.setExecutor (executor); 456 s1.start(); 457 458 if (port == 0) 459 port = s1.getAddress().getPort(); 460 else { 461 if (s1.getAddress().getPort() != port) 462 throw new RuntimeException("Error wrong port"); 463 System.out.println("Port was assigned by Driver"); 464 } 465 System.out.println("HTTP server port = " + port); 466 httproot = "http://127.0.0.1:" + port + "/files/"; 467 redirectroot = "http://127.0.0.1:" + port + "/redirect/"; 468 uri = new URI(httproot); 469 fileuri = httproot + "foo.txt"; 470 } 471 472 static class RedirectHandler implements HttpHandler { 473 474 String root; 475 int count = 0; 476 477 RedirectHandler(String root) { 478 this.root = root; 479 } 480 481 synchronized int count() { 482 return count; 483 } 484 485 synchronized void increment() { 486 count++; 487 } 488 489 @Override 490 public synchronized void handle(HttpExchange t) 491 throws IOException { 492 byte[] buf = new byte[2048]; 493 System.out.println("Server: " + t.getRequestURI()); 494 try (InputStream is = t.getRequestBody()) { 495 while (is.read(buf) != -1) ; 496 } 497 increment(); 498 if (count() == 1) { 499 Headers map = t.getResponseHeaders(); 500 String redirect = "/redirect/bar.txt"; 501 map.add("Location", redirect); 502 t.sendResponseHeaders(301, -1); 503 t.close(); 504 } else { 505 String response = "Hello world"; 506 t.sendResponseHeaders(200, response.length()); 507 OutputStream os = t.getResponseBody(); 508 os.write(response.getBytes(StandardCharsets.ISO_8859_1)); 509 t.close(); 510 } 511 } 512 } 513 }