| /* |
| * Copyright (c) 2015, 2016, 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 jdk.incubator.httpclient |
| * java.logging |
| * jdk.httpserver |
| * @library /lib/testlibrary/ |
| * @build jdk.testlibrary.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.HttpContext; |
| 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.InetSocketAddress; |
| import java.net.ProxySelector; |
| import java.net.URI; |
| import java.net.URLClassLoader; |
| import java.net.URL; |
| import jdk.incubator.http.HttpHeaders; |
| import jdk.incubator.http.HttpClient; |
| import jdk.incubator.http.HttpRequest; |
| import jdk.incubator.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.Executor; |
| 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 jdk.incubator.http.HttpResponse.BodyHandler.asString; |
| |
| /** |
| * Security checks test |
| */ |
| public class Security { |
| |
| static HttpServer s1 = null; |
| static ExecutorService executor=null; |
| static int port, proxyPort; |
| static HttpClient client; |
| static String httproot, fileuri, fileroot, redirectroot; |
| static List<HttpClient> clients = new LinkedList<>(); |
| static URI uri; |
| |
| interface Test { |
| void execute() throws IOException, InterruptedException; |
| } |
| |
| static class TestAndResult { |
| Test test; |
| boolean result; |
| |
| TestAndResult (Test t, boolean result) { |
| this.test = t; |
| this.result = result; |
| } |
| } |
| |
| static TestAndResult test(boolean result, Test t) { |
| return new TestAndResult(t, result); |
| } |
| |
| static TestAndResult[] tests; |
| 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.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 getProxy(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 void setupTests() { |
| tests = new TestAndResult[]{ |
| // (0) policy does not have permission for file. Should fail |
| test(false, () -> { // Policy 0 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (1) policy has permission for file URL |
| test(true, () -> { //Policy 1 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (2) policy has permission for all file URLs under /files |
| test(true, () -> { // Policy 2 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (3) policy has permission for first URL but not redirected URL |
| test(false, () -> { // Policy 3 |
| URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (4) policy has permission for both first URL and redirected URL |
| test(true, () -> { // Policy 4 |
| URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (5) policy has permission for redirected but not first URL |
| test(false, () -> { // Policy 5 |
| URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (6) policy has permission for file URL, but not method |
| test(false, () -> { //Policy 6 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (7) policy has permission for file URL, method, but not header |
| test(false, () -> { //Policy 7 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u) |
| .header("X-Foo", "bar") |
| .GET() |
| .build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (8) policy has permission for file URL, method and header |
| test(true, () -> { //Policy 8 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u) |
| .header("X-Foo", "bar") |
| .GET() |
| .build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (9) policy has permission for file URL, method and header |
| test(true, () -> { //Policy 9 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u) |
| .headers("X-Foo", "bar", "X-Bar", "foo") |
| .GET() |
| .build(); |
| HttpResponse<?> response = client.send(request, asString()); |
| }), |
| // (10) policy has permission for destination URL but not for proxy |
| test(false, () -> { //Policy 10 |
| directProxyTest(proxyPort, true); |
| }), |
| // (11) policy has permission for both destination URL and proxy |
| test(true, () -> { //Policy 11 |
| directProxyTest(proxyPort, true); |
| }), |
| // (12) policy has permission for both destination URL and proxy |
| test(false, () -> { //Policy 11 |
| directProxyTest(proxyPort, false); |
| }), |
| // (13) async version of test 0 |
| test(false, () -> { // Policy 0 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| try { |
| HttpResponse<?> response = client.sendAsync(request, asString()).get(); |
| } catch (ExecutionException e) { |
| if (e.getCause() instanceof SecurityException) { |
| throw (SecurityException)e.getCause(); |
| } else { |
| throw new RuntimeException(e); |
| } |
| } |
| }), |
| // (14) async version of test 1 |
| test(true, () -> { //Policy 1 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| try { |
| HttpResponse<?> response = client.sendAsync(request, asString()).get(); |
| } 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. |
| test(false, () -> { //Policy 12 |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u).GET().build(); |
| HttpResponse.BodyHandler<String> sth = asString(); |
| |
| CompletableFuture<HttpResponse<String>> cf = |
| client.sendAsync(request, new HttpResponse.BodyHandler<String>() { |
| @Override |
| public HttpResponse.BodyProcessor<String> apply(int status, HttpHeaders responseHeaders) { |
| final HttpResponse.BodyProcessor<String> stproc = sth.apply(status, responseHeaders); |
| return new HttpResponse.BodyProcessor<String>() { |
| @Override |
| public CompletionStage<String> getBody() { |
| return stproc.getBody(); |
| } |
| @Override |
| public void onNext(ByteBuffer 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 { |
| cf.join(); |
| } catch (CompletionException e) { |
| Throwable t = e.getCause(); |
| if (t instanceof SecurityException) |
| throw (SecurityException)t; |
| else |
| throw new RuntimeException(t); |
| } |
| }) |
| }; |
| } |
| |
| private static void directProxyTest(int proxyPort, boolean samePort) |
| throws IOException, InterruptedException |
| { |
| Object proxy = null; |
| try { |
| proxy = getProxy(proxyPort, true); |
| } catch (BindException e) { |
| System.out.println("Bind failed"); |
| throw e; |
| } catch (Throwable ee) { |
| throw new RuntimeException(ee); |
| } |
| System.out.println("Proxy port = " + proxyPort); |
| if (!samePort) |
| proxyPort++; |
| |
| InetSocketAddress addr = new InetSocketAddress("127.0.0.1", proxyPort); |
| HttpClient cl = HttpClient.newBuilder() |
| .proxy(ProxySelector.of(addr)) |
| .build(); |
| clients.add(cl); |
| |
| URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt"); |
| HttpRequest request = HttpRequest.newBuilder(u) |
| .headers("X-Foo", "bar", "X-Bar", "foo") |
| .build(); |
| HttpResponse<?> response = cl.send(request, asString()); |
| } |
| |
| static void runtest(Test r, String policy, boolean succeeds) { |
| System.out.println("Using policy file: " + policy); |
| try { |
| r.execute(); |
| if (!succeeds) { |
| System.out.println("FAILED: expected security exception"); |
| throw new RuntimeException("Failed"); |
| } |
| System.out.println (policy + " succeeded as expected"); |
| } catch (BindException e) { |
| System.exit(10); |
| } catch (SecurityException e) { |
| if (succeeds) { |
| System.out.println("FAILED"); |
| throw new RuntimeException(e); |
| } |
| System.out.println (policy + " threw exception as expected"); |
| } catch (IOException | InterruptedException ee) { |
| throw new RuntimeException(ee); |
| } |
| } |
| |
| 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 { |
| setupTests(); |
| TestAndResult tr = tests[testnum]; |
| runtest(tr.test, policy, tr.result); |
| } finally { |
| s1.stop(0); |
| executor.shutdownNow(); |
| for (HttpClient client : clients) { |
| Executor e = client.executor(); |
| if (e instanceof ExecutorService) { |
| ((ExecutorService)e).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 (port); |
| s1 = HttpServer.create (addr, 0); |
| if (s1 instanceof HttpsServer) { |
| throw new RuntimeException ("should not be httpsserver"); |
| } |
| HttpHandler h = new FileServerHandler (root); |
| HttpContext c = s1.createContext ("/files", h); |
| |
| HttpHandler h1 = new RedirectHandler ("/redirect"); |
| HttpContext c1 = s1.createContext ("/redirect", h1); |
| |
| 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://127.0.0.1:" + port + "/files/"; |
| redirectroot = "http://127.0.0.1:" + 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 { |
| byte[] buf = new byte[2048]; |
| System.out.println("Server: " + t.getRequestURI()); |
| try (InputStream is = t.getRequestBody()) { |
| while (is.read(buf) != -1) ; |
| } |
| 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(); |
| } |
| } |
| } |
| } |