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 }