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 }