/* * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8087112 * @modules java.net.http * java.logging * jdk.httpserver * @library /test/lib * @build jdk.test.lib.net.SimpleSSLContext * @compile ../../../../com/sun/net/httpserver/LogFilter.java * @compile ../../../../com/sun/net/httpserver/FileServerHandler.java * @compile ../ProxyServer.java * * @run main/othervm/secure=java.lang.SecurityManager/policy=0.policy Security 0 * @run main/othervm/secure=java.lang.SecurityManager/policy=2.policy Security 2 * @run main/othervm/secure=java.lang.SecurityManager/policy=3.policy Security 3 * @run main/othervm/secure=java.lang.SecurityManager/policy=4.policy Security 4 * @run main/othervm/secure=java.lang.SecurityManager/policy=5.policy Security 5 * @run main/othervm/secure=java.lang.SecurityManager/policy=6.policy Security 6 * @run main/othervm/secure=java.lang.SecurityManager/policy=7.policy Security 7 * @run main/othervm/secure=java.lang.SecurityManager/policy=8.policy Security 8 * @run main/othervm/secure=java.lang.SecurityManager/policy=9.policy Security 9 * @run main/othervm/secure=java.lang.SecurityManager/policy=0.policy Security 13 * @run main/othervm/secure=java.lang.SecurityManager/policy=14.policy Security 14 * @run main/othervm/secure=java.lang.SecurityManager/policy=15.policy -Djava.security.debug=access:domain,failure Security 15 */ // Tests 1, 10, 11 and 12 executed from Driver import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsServer; import java.io.IOException; import java.io.InputStream; import java.io.File; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.net.BindException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ProxySelector; import java.net.URI; import java.net.URLClassLoader; import java.net.URL; import java.net.http.HttpHeaders; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.ByteBuffer; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.Flow; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import java.lang.reflect.InvocationTargetException; import static java.net.http.HttpResponse.BodyHandlers.ofString; /** * Security checks test */ public class Security { static HttpServer s1; static ExecutorService executor; static int port, proxyPort; static HttpClient client; static String httproot, fileuri, fileroot, redirectroot; static List clients = new LinkedList<>(); static URI uri; interface ThrowingRunnable { void run() throws Throwable; } static class TestAndResult { private final ThrowingRunnable runnable; private final boolean expectSecurityException; TestAndResult(boolean expectSecurityException, ThrowingRunnable runnable) { this.expectSecurityException = expectSecurityException; this.runnable = runnable; } static TestAndResult of(boolean expectSecurityException, ThrowingRunnable runnable) { return new TestAndResult(expectSecurityException, runnable); } void runWithPolicy(String policy) { System.out.println("Using policy file: " + policy); try { runnable.run(); if (expectSecurityException) { String msg = "FAILED: expected security exception not thrown"; System.out.println(msg); throw new RuntimeException(msg); } System.out.println (policy + " succeeded as expected"); } catch (BindException e) { System.exit(10); } catch (SecurityException e) { if (!expectSecurityException) { System.out.println("UNEXPECTED security Exception: " + e); throw new RuntimeException("UNEXPECTED security Exception", e); } System.out.println(policy + " threw SecurityException as expected: " + e); } catch (Throwable t) { throw new AssertionError(t); } } } static TestAndResult[] tests = createTests(); static String testclasses; static File subdir; /** * The ProxyServer class is compiled by jtreg, but we want to * move it so it is not on the application claspath. We want to * load it through a separate classloader so that it has a separate * protection domain and security permissions. * * Its permissions are in the second grant block in each policy file */ static void setupProxy() throws IOException, ClassNotFoundException, NoSuchMethodException { testclasses = System.getProperty("test.classes"); subdir = new File (testclasses, "proxydir"); subdir.mkdir(); movefile("ProxyServer.class"); movefile("ProxyServer$Connection.class"); movefile("ProxyServer$1.class"); URL url = subdir.toURI().toURL(); System.out.println("URL for class loader = " + url); URLClassLoader urlc = new URLClassLoader(new URL[] {url}); proxyClass = Class.forName("ProxyServer", true, urlc); proxyConstructor = proxyClass.getConstructor(Integer.class, Boolean.class); } static void movefile(String f) throws IOException { Path src = Paths.get(testclasses, f); Path dest = subdir.toPath().resolve(f); if (!dest.toFile().exists()) { System.out.printf("moving %s to %s\n", src.toString(), dest.toString()); Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING); } else if (src.toFile().exists()) { System.out.printf("%s exists, deleting %s\n", dest.toString(), src.toString()); Files.delete(src); } else { System.out.printf("NOT moving %s to %s\n", src.toString(), dest.toString()); } } static Object createProxy(int port, boolean b) throws Throwable { try { return proxyConstructor.newInstance(port, b); } catch (InvocationTargetException e) { throw e.getTargetException(); } } static Class proxyClass; static Constructor proxyConstructor; static TestAndResult[] createTests() { return new TestAndResult[] { // (0) policy does not have permission for file. Should fail TestAndResult.of(true, () -> { // Policy 0 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (1) policy has permission for file URL TestAndResult.of(false, () -> { //Policy 1 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (2) policy has permission for all file URLs under /files TestAndResult.of(false, () -> { // Policy 2 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (3) policy has permission for first URL but not redirected URL TestAndResult.of(true, () -> { // Policy 3 URI u = URI.create("http://localhost:" + port + "/redirect/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (4) policy has permission for both first URL and redirected URL TestAndResult.of(false, () -> { // Policy 4 URI u = URI.create("http://localhost:" + port + "/redirect/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (5) policy has permission for redirected but not first URL TestAndResult.of(true, () -> { // Policy 5 URI u = URI.create("http://localhost:" + port + "/redirect/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (6) policy has permission for file URL, but not method TestAndResult.of(true, () -> { //Policy 6 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (7) policy has permission for file URL, method, but not header TestAndResult.of(true, () -> { //Policy 7 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u) .header("X-Foo", "bar") .GET() .build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (8) policy has permission for file URL, method and header TestAndResult.of(false, () -> { //Policy 8 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u) .header("X-Foo", "bar") .GET() .build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (9) policy has permission for file URL, method and header TestAndResult.of(false, () -> { //Policy 9 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u) .headers("X-Foo", "bar", "X-Bar", "foo") .GET() .build(); HttpResponse response = client.send(request, ofString()); System.out.println("Received response:" + response); }), // (10) policy has permission for destination URL but not for proxy TestAndResult.of(true, () -> { //Policy 10 directProxyTest(proxyPort, true); }), // (11) policy has permission for both destination URL and proxy TestAndResult.of(false, () -> { //Policy 11 directProxyTest(proxyPort, true); }), // (12) policy has permission for both destination URL and proxy TestAndResult.of(true, () -> { //Policy 12 ( 11 & 12 are the same ) directProxyTest(proxyPort, false); }), // (13) async version of test 0 TestAndResult.of(true, () -> { // Policy 0 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); try { HttpResponse response = client.sendAsync(request, ofString()).get(); System.out.println("Received response:" + response); } catch (ExecutionException e) { if (e.getCause() instanceof SecurityException) { throw (SecurityException)e.getCause(); } else { throw new RuntimeException(e); } } }), // (14) async version of test 1 TestAndResult.of(false, () -> { //Policy 1 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); try { HttpResponse response = client.sendAsync(request, ofString()).get(); System.out.println("Received response:" + response); } catch (ExecutionException e) { if (e.getCause() instanceof SecurityException) { throw (SecurityException)e.getCause(); } else { throw new RuntimeException(e); } } }), // (15) check that user provided unprivileged code running on a worker // thread does not gain ungranted privileges. TestAndResult.of(true, () -> { //Policy 12 URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u).GET().build(); HttpResponse.BodyHandler sth = ofString(); CompletableFuture> cf = client.sendAsync(request, new HttpResponse.BodyHandler() { @Override public HttpResponse.BodySubscriber apply(HttpResponse.ResponseInfo rinfo) { final HttpResponse.BodySubscriber stproc = sth.apply(rinfo); return new HttpResponse.BodySubscriber() { @Override public CompletionStage getBody() { return stproc.getBody(); } @Override public void onNext(List item) { SecurityManager sm = System.getSecurityManager(); // should succeed. sm.checkPermission(new RuntimePermission("foobar")); // do some mischief here System.setSecurityManager(null); System.setSecurityManager(sm); // problem if we get this far stproc.onNext(item); } @Override public void onSubscribe(Flow.Subscription subscription) { stproc.onSubscribe(subscription); } @Override public void onError(Throwable throwable) { stproc.onError(throwable); } @Override public void onComplete() { stproc.onComplete(); } }; } } ); try { HttpResponse response = cf.join(); System.out.println("Received response:" + response); } catch (CompletionException e) { Throwable t = e.getCause(); if (t instanceof SecurityException) throw (SecurityException)t; else if ((t instanceof IOException) && (t.getCause() instanceof SecurityException)) throw ((SecurityException)t.getCause()); else throw new RuntimeException(t); } }) }; } private static void directProxyTest(int proxyPort, boolean samePort) throws IOException, InterruptedException { System.out.println("proxyPort:" + proxyPort + ", samePort:" + samePort); int p = proxyPort; if (samePort) { Object proxy; try { proxy = createProxy(p, true); } catch (BindException e) { System.out.println("Bind failed"); throw e; } catch (Throwable ee) { throw new RuntimeException(ee); } } else { while (p == proxyPort || p == port) { // avoid ports that may be granted permission p++; if (p > 65535) { p = 32000; // overflow } } } System.out.println("Proxy port, p:" + p); InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), p); HttpClient cl = HttpClient.newBuilder() .proxy(ProxySelector.of(addr)) .build(); clients.add(cl); URI u = URI.create("http://localhost:" + port + "/files/foo.txt"); HttpRequest request = HttpRequest.newBuilder(u) .headers("X-Foo", "bar", "X-Bar", "foo") .build(); HttpResponse response = cl.send(request, ofString()); System.out.println("Received response:" + response); } public static void main(String[] args) throws Exception { try { initServer(); setupProxy(); } catch (BindException e) { System.exit(10); } fileroot = System.getProperty("test.src")+ "/docs"; int testnum = Integer.parseInt(args[0]); String policy = args[0]; client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.ALWAYS) .build(); clients.add(client); try { TestAndResult tr = tests[testnum]; tr.runWithPolicy(policy); } finally { s1.stop(0); executor.shutdownNow(); } } public static void initServer() throws Exception { String portstring = System.getProperty("port.number"); port = portstring != null ? Integer.parseInt(portstring) : 0; portstring = System.getProperty("port.number1"); proxyPort = portstring != null ? Integer.parseInt(portstring) : 0; Logger logger = Logger.getLogger("com.sun.net.httpserver"); ConsoleHandler ch = new ConsoleHandler(); logger.setLevel(Level.ALL); ch.setLevel(Level.ALL); logger.addHandler(ch); String root = System.getProperty("test.src")+ "/docs"; InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), port); s1 = HttpServer.create (addr, 0); if (s1 instanceof HttpsServer) { throw new RuntimeException("should not be httpsserver"); } s1.createContext("/files", new FileServerHandler(root)); s1.createContext("/redirect", new RedirectHandler("/redirect")); executor = Executors.newCachedThreadPool(); s1.setExecutor(executor); s1.start(); if (port == 0) port = s1.getAddress().getPort(); else { if (s1.getAddress().getPort() != port) throw new RuntimeException("Error wrong port"); System.out.println("Port was assigned by Driver"); } System.out.println("HTTP server port = " + port); httproot = "http://localhost:" + port + "/files/"; redirectroot = "http://localhost:" + port + "/redirect/"; uri = new URI(httproot); fileuri = httproot + "foo.txt"; } static class RedirectHandler implements HttpHandler { String root; int count = 0; RedirectHandler(String root) { this.root = root; } synchronized int count() { return count; } synchronized void increment() { count++; } @Override public synchronized void handle(HttpExchange t) throws IOException { System.out.println("Server: " + t.getRequestURI()); try (InputStream is = t.getRequestBody()) { is.readAllBytes(); } increment(); if (count() == 1) { Headers map = t.getResponseHeaders(); String redirect = "/redirect/bar.txt"; map.add("Location", redirect); t.sendResponseHeaders(301, -1); t.close(); } else { String response = "Hello world"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes(StandardCharsets.ISO_8859_1)); t.close(); } } } }