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